Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
awilliam
GitHub Repository: awilliam/linux-vfio
Path: blob/master/fs/cifs/cifs_dfs_ref.c
15112 views
1
/*
2
* Contains the CIFS DFS referral mounting routines used for handling
3
* traversal via DFS junction point
4
*
5
* Copyright (c) 2007 Igor Mammedov
6
* Copyright (C) International Business Machines Corp., 2008
7
* Author(s): Igor Mammedov ([email protected])
8
* Steve French ([email protected])
9
* This program is free software; you can redistribute it and/or
10
* modify it under the terms of the GNU General Public License
11
* as published by the Free Software Foundation; either version
12
* 2 of the License, or (at your option) any later version.
13
*/
14
15
#include <linux/dcache.h>
16
#include <linux/mount.h>
17
#include <linux/namei.h>
18
#include <linux/slab.h>
19
#include <linux/vfs.h>
20
#include <linux/fs.h>
21
#include "cifsglob.h"
22
#include "cifsproto.h"
23
#include "cifsfs.h"
24
#include "dns_resolve.h"
25
#include "cifs_debug.h"
26
27
static LIST_HEAD(cifs_dfs_automount_list);
28
29
static void cifs_dfs_expire_automounts(struct work_struct *work);
30
static DECLARE_DELAYED_WORK(cifs_dfs_automount_task,
31
cifs_dfs_expire_automounts);
32
static int cifs_dfs_mountpoint_expiry_timeout = 500 * HZ;
33
34
static void cifs_dfs_expire_automounts(struct work_struct *work)
35
{
36
struct list_head *list = &cifs_dfs_automount_list;
37
38
mark_mounts_for_expiry(list);
39
if (!list_empty(list))
40
schedule_delayed_work(&cifs_dfs_automount_task,
41
cifs_dfs_mountpoint_expiry_timeout);
42
}
43
44
void cifs_dfs_release_automount_timer(void)
45
{
46
BUG_ON(!list_empty(&cifs_dfs_automount_list));
47
cancel_delayed_work_sync(&cifs_dfs_automount_task);
48
}
49
50
/**
51
* cifs_get_share_name - extracts share name from UNC
52
* @node_name: pointer to UNC string
53
*
54
* Extracts sharename form full UNC.
55
* i.e. strips from UNC trailing path that is not part of share
56
* name and fixup missing '\' in the beginning of DFS node refferal
57
* if necessary.
58
* Returns pointer to share name on success or ERR_PTR on error.
59
* Caller is responsible for freeing returned string.
60
*/
61
static char *cifs_get_share_name(const char *node_name)
62
{
63
int len;
64
char *UNC;
65
char *pSep;
66
67
len = strlen(node_name);
68
UNC = kmalloc(len+2 /*for term null and additional \ if it's missed */,
69
GFP_KERNEL);
70
if (!UNC)
71
return ERR_PTR(-ENOMEM);
72
73
/* get share name and server name */
74
if (node_name[1] != '\\') {
75
UNC[0] = '\\';
76
strncpy(UNC+1, node_name, len);
77
len++;
78
UNC[len] = 0;
79
} else {
80
strncpy(UNC, node_name, len);
81
UNC[len] = 0;
82
}
83
84
/* find server name end */
85
pSep = memchr(UNC+2, '\\', len-2);
86
if (!pSep) {
87
cERROR(1, "%s: no server name end in node name: %s",
88
__func__, node_name);
89
kfree(UNC);
90
return ERR_PTR(-EINVAL);
91
}
92
93
/* find sharename end */
94
pSep++;
95
pSep = memchr(UNC+(pSep-UNC), '\\', len-(pSep-UNC));
96
if (pSep) {
97
/* trim path up to sharename end
98
* now we have share name in UNC */
99
*pSep = 0;
100
}
101
102
return UNC;
103
}
104
105
106
/**
107
* cifs_compose_mount_options - creates mount options for refferral
108
* @sb_mountdata: parent/root DFS mount options (template)
109
* @fullpath: full path in UNC format
110
* @ref: server's referral
111
* @devname: pointer for saving device name
112
*
113
* creates mount options for submount based on template options sb_mountdata
114
* and replacing unc,ip,prefixpath options with ones we've got form ref_unc.
115
*
116
* Returns: pointer to new mount options or ERR_PTR.
117
* Caller is responcible for freeing retunrned value if it is not error.
118
*/
119
char *cifs_compose_mount_options(const char *sb_mountdata,
120
const char *fullpath,
121
const struct dfs_info3_param *ref,
122
char **devname)
123
{
124
int rc;
125
char *mountdata = NULL;
126
int md_len;
127
char *tkn_e;
128
char *srvIP = NULL;
129
char sep = ',';
130
int off, noff;
131
132
if (sb_mountdata == NULL)
133
return ERR_PTR(-EINVAL);
134
135
*devname = cifs_get_share_name(ref->node_name);
136
if (IS_ERR(*devname)) {
137
rc = PTR_ERR(*devname);
138
*devname = NULL;
139
goto compose_mount_options_err;
140
}
141
142
rc = dns_resolve_server_name_to_ip(*devname, &srvIP);
143
if (rc < 0) {
144
cERROR(1, "%s: Failed to resolve server part of %s to IP: %d",
145
__func__, *devname, rc);
146
goto compose_mount_options_err;
147
}
148
/* md_len = strlen(...) + 12 for 'sep+prefixpath='
149
* assuming that we have 'unc=' and 'ip=' in
150
* the original sb_mountdata
151
*/
152
md_len = strlen(sb_mountdata) + rc + strlen(ref->node_name) + 12;
153
mountdata = kzalloc(md_len+1, GFP_KERNEL);
154
if (mountdata == NULL) {
155
rc = -ENOMEM;
156
goto compose_mount_options_err;
157
}
158
159
/* copy all options except of unc,ip,prefixpath */
160
off = 0;
161
if (strncmp(sb_mountdata, "sep=", 4) == 0) {
162
sep = sb_mountdata[4];
163
strncpy(mountdata, sb_mountdata, 5);
164
off += 5;
165
}
166
167
do {
168
tkn_e = strchr(sb_mountdata + off, sep);
169
if (tkn_e == NULL)
170
noff = strlen(sb_mountdata + off);
171
else
172
noff = tkn_e - (sb_mountdata + off) + 1;
173
174
if (strnicmp(sb_mountdata + off, "unc=", 4) == 0) {
175
off += noff;
176
continue;
177
}
178
if (strnicmp(sb_mountdata + off, "ip=", 3) == 0) {
179
off += noff;
180
continue;
181
}
182
if (strnicmp(sb_mountdata + off, "prefixpath=", 11) == 0) {
183
off += noff;
184
continue;
185
}
186
strncat(mountdata, sb_mountdata + off, noff);
187
off += noff;
188
} while (tkn_e);
189
strcat(mountdata, sb_mountdata + off);
190
mountdata[md_len] = '\0';
191
192
/* copy new IP and ref share name */
193
if (mountdata[strlen(mountdata) - 1] != sep)
194
strncat(mountdata, &sep, 1);
195
strcat(mountdata, "ip=");
196
strcat(mountdata, srvIP);
197
strncat(mountdata, &sep, 1);
198
strcat(mountdata, "unc=");
199
strcat(mountdata, *devname);
200
201
/* find & copy prefixpath */
202
tkn_e = strchr(ref->node_name + 2, '\\');
203
if (tkn_e == NULL) {
204
/* invalid unc, missing share name*/
205
rc = -EINVAL;
206
goto compose_mount_options_err;
207
}
208
209
tkn_e = strchr(tkn_e + 1, '\\');
210
if (tkn_e || (strlen(fullpath) - ref->path_consumed)) {
211
strncat(mountdata, &sep, 1);
212
strcat(mountdata, "prefixpath=");
213
if (tkn_e)
214
strcat(mountdata, tkn_e + 1);
215
strcat(mountdata, fullpath + ref->path_consumed);
216
}
217
218
/*cFYI(1, "%s: parent mountdata: %s", __func__,sb_mountdata);*/
219
/*cFYI(1, "%s: submount mountdata: %s", __func__, mountdata );*/
220
221
compose_mount_options_out:
222
kfree(srvIP);
223
return mountdata;
224
225
compose_mount_options_err:
226
kfree(mountdata);
227
mountdata = ERR_PTR(rc);
228
goto compose_mount_options_out;
229
}
230
231
/**
232
* cifs_dfs_do_refmount - mounts specified path using provided refferal
233
* @cifs_sb: parent/root superblock
234
* @fullpath: full path in UNC format
235
* @ref: server's referral
236
*/
237
static struct vfsmount *cifs_dfs_do_refmount(struct cifs_sb_info *cifs_sb,
238
const char *fullpath, const struct dfs_info3_param *ref)
239
{
240
struct vfsmount *mnt;
241
char *mountdata;
242
char *devname = NULL;
243
244
/* strip first '\' from fullpath */
245
mountdata = cifs_compose_mount_options(cifs_sb->mountdata,
246
fullpath + 1, ref, &devname);
247
248
if (IS_ERR(mountdata))
249
return (struct vfsmount *)mountdata;
250
251
mnt = vfs_kern_mount(&cifs_fs_type, 0, devname, mountdata);
252
kfree(mountdata);
253
kfree(devname);
254
return mnt;
255
256
}
257
258
static void dump_referral(const struct dfs_info3_param *ref)
259
{
260
cFYI(1, "DFS: ref path: %s", ref->path_name);
261
cFYI(1, "DFS: node path: %s", ref->node_name);
262
cFYI(1, "DFS: fl: %hd, srv_type: %hd", ref->flags, ref->server_type);
263
cFYI(1, "DFS: ref_flags: %hd, path_consumed: %hd", ref->ref_flag,
264
ref->path_consumed);
265
}
266
267
/*
268
* Create a vfsmount that we can automount
269
*/
270
static struct vfsmount *cifs_dfs_do_automount(struct dentry *mntpt)
271
{
272
struct dfs_info3_param *referrals = NULL;
273
unsigned int num_referrals = 0;
274
struct cifs_sb_info *cifs_sb;
275
struct cifs_ses *ses;
276
char *full_path;
277
int xid, i;
278
int rc;
279
struct vfsmount *mnt;
280
struct tcon_link *tlink;
281
282
cFYI(1, "in %s", __func__);
283
BUG_ON(IS_ROOT(mntpt));
284
285
/*
286
* The MSDFS spec states that paths in DFS referral requests and
287
* responses must be prefixed by a single '\' character instead of
288
* the double backslashes usually used in the UNC. This function
289
* gives us the latter, so we must adjust the result.
290
*/
291
mnt = ERR_PTR(-ENOMEM);
292
full_path = build_path_from_dentry(mntpt);
293
if (full_path == NULL)
294
goto cdda_exit;
295
296
cifs_sb = CIFS_SB(mntpt->d_inode->i_sb);
297
tlink = cifs_sb_tlink(cifs_sb);
298
if (IS_ERR(tlink)) {
299
mnt = ERR_CAST(tlink);
300
goto free_full_path;
301
}
302
ses = tlink_tcon(tlink)->ses;
303
304
xid = GetXid();
305
rc = get_dfs_path(xid, ses, full_path + 1, cifs_sb->local_nls,
306
&num_referrals, &referrals,
307
cifs_sb->mnt_cifs_flags & CIFS_MOUNT_MAP_SPECIAL_CHR);
308
FreeXid(xid);
309
310
cifs_put_tlink(tlink);
311
312
mnt = ERR_PTR(-ENOENT);
313
for (i = 0; i < num_referrals; i++) {
314
int len;
315
dump_referral(referrals + i);
316
/* connect to a node */
317
len = strlen(referrals[i].node_name);
318
if (len < 2) {
319
cERROR(1, "%s: Net Address path too short: %s",
320
__func__, referrals[i].node_name);
321
mnt = ERR_PTR(-EINVAL);
322
break;
323
}
324
mnt = cifs_dfs_do_refmount(cifs_sb,
325
full_path, referrals + i);
326
cFYI(1, "%s: cifs_dfs_do_refmount:%s , mnt:%p", __func__,
327
referrals[i].node_name, mnt);
328
if (!IS_ERR(mnt))
329
goto success;
330
}
331
332
/* no valid submounts were found; return error from get_dfs_path() by
333
* preference */
334
if (rc != 0)
335
mnt = ERR_PTR(rc);
336
337
success:
338
free_dfs_info_array(referrals, num_referrals);
339
free_full_path:
340
kfree(full_path);
341
cdda_exit:
342
cFYI(1, "leaving %s" , __func__);
343
return mnt;
344
}
345
346
/*
347
* Attempt to automount the referral
348
*/
349
struct vfsmount *cifs_dfs_d_automount(struct path *path)
350
{
351
struct vfsmount *newmnt;
352
353
cFYI(1, "in %s", __func__);
354
355
newmnt = cifs_dfs_do_automount(path->dentry);
356
if (IS_ERR(newmnt)) {
357
cFYI(1, "leaving %s [automount failed]" , __func__);
358
return newmnt;
359
}
360
361
mntget(newmnt); /* prevent immediate expiration */
362
mnt_set_expiry(newmnt, &cifs_dfs_automount_list);
363
schedule_delayed_work(&cifs_dfs_automount_task,
364
cifs_dfs_mountpoint_expiry_timeout);
365
cFYI(1, "leaving %s [ok]" , __func__);
366
return newmnt;
367
}
368
369
const struct inode_operations cifs_dfs_referral_inode_operations = {
370
};
371
372