xref: /reactos/base/services/nfsd/lookup.c (revision 84ccccab)
1 /* NFSv4.1 client for Windows
2  * Copyright � 2012 The Regents of the University of Michigan
3  *
4  * Olga Kornievskaia <aglo@umich.edu>
5  * Casey Bodley <cbodley@umich.edu>
6  *
7  * This library is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation; either version 2.1 of the License, or (at
10  * your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful, but
13  * without any warranty; without even the implied warranty of merchantability
14  * or fitness for a particular purpose.  See the GNU Lesser General Public
15  * License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this library; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20  */
21 
22 #include <windows.h>
23 #include <strsafe.h>
24 #include <time.h>
25 
26 #include "nfs41_compound.h"
27 #include "nfs41_ops.h"
28 #include "name_cache.h"
29 #include "util.h"
30 #include "daemon_debug.h"
31 
32 
33 #define LULVL 2 /* dprintf level for lookup logging */
34 
35 
36 #define MAX_LOOKUP_COMPONENTS 8
37 
38 /* map NFS4ERR_MOVED to an arbitrary windows error */
39 #define ERROR_FILESYSTEM_ABSENT ERROR_DEVICE_REMOVED
40 
41 struct lookup_referral {
42     nfs41_path_fh           parent;
43     nfs41_component         name;
44 };
45 
46 
47 typedef struct __nfs41_lookup_component_args {
48     nfs41_sequence_args     sequence;
49     nfs41_putfh_args        putfh;
50     nfs41_lookup_args       lookup[MAX_LOOKUP_COMPONENTS];
51     nfs41_getattr_args      getrootattr;
52     nfs41_getattr_args      getattr[MAX_LOOKUP_COMPONENTS];
53     bitmap4                 attr_request;
54 } nfs41_lookup_component_args;
55 
56 typedef struct __nfs41_lookup_component_res {
57     nfs41_sequence_res      sequence;
58     nfs41_putfh_res         putfh;
59     nfs41_lookup_res        lookup[MAX_LOOKUP_COMPONENTS];
60     nfs41_getfh_res         getrootfh;
61     nfs41_getfh_res         getfh[MAX_LOOKUP_COMPONENTS];
62     nfs41_path_fh           root;
63     nfs41_path_fh           file[MAX_LOOKUP_COMPONENTS];
64     nfs41_getattr_res       getrootattr;
65     nfs41_getattr_res       getattr[MAX_LOOKUP_COMPONENTS];
66     nfs41_file_info         rootinfo;
67     nfs41_file_info         info[MAX_LOOKUP_COMPONENTS];
68     struct lookup_referral  *referral;
69 } nfs41_lookup_component_res;
70 
71 
72 static void init_component_args(
73     IN nfs41_lookup_component_args *args,
74     IN nfs41_lookup_component_res *res,
75     IN nfs41_abs_path *path,
76     IN struct lookup_referral *referral)
77 {
78     uint32_t i;
79 
80     args->attr_request.count = 2;
81     args->attr_request.arr[0] = FATTR4_WORD0_TYPE
82         | FATTR4_WORD0_CHANGE | FATTR4_WORD0_SIZE
83         | FATTR4_WORD0_FSID | FATTR4_WORD0_FILEID
84         | FATTR4_WORD0_HIDDEN | FATTR4_WORD0_ARCHIVE;
85     args->attr_request.arr[1] = FATTR4_WORD1_MODE
86         | FATTR4_WORD1_NUMLINKS | FATTR4_WORD1_SYSTEM
87         | FATTR4_WORD1_TIME_ACCESS | FATTR4_WORD1_TIME_CREATE
88         | FATTR4_WORD1_TIME_MODIFY;
89 
90     args->getrootattr.attr_request = &args->attr_request;
91     res->root.path = path;
92     res->getrootfh.fh = &res->root.fh;
93     res->getrootattr.info = &res->rootinfo;
94     res->getrootattr.obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
95     res->referral = referral;
96 
97     for (i = 0; i < MAX_LOOKUP_COMPONENTS; i++) {
98         args->getattr[i].attr_request = &args->attr_request;
99         res->file[i].path = path;
100         args->lookup[i].name = &res->file[i].name;
101         res->getfh[i].fh = &res->file[i].fh;
102         res->getattr[i].info = &res->info[i];
103         res->getattr[i].obj_attributes.attr_vals_len = NFS4_OPAQUE_LIMIT;
104     }
105 }
106 
107 static int lookup_rpc(
108     IN nfs41_session *session,
109     IN nfs41_path_fh *dir,
110     IN uint32_t component_count,
111     IN nfs41_lookup_component_args *args,
112     OUT nfs41_lookup_component_res *res)
113 {
114     int status;
115     uint32_t i;
116     nfs41_compound compound;
117     nfs_argop4 argops[4+MAX_LOOKUP_COMPONENTS*3];
118     nfs_resop4 resops[4+MAX_LOOKUP_COMPONENTS*3];
119 
120     compound_init(&compound, argops, resops, "lookup");
121 
122     compound_add_op(&compound, OP_SEQUENCE, &args->sequence, &res->sequence);
123     nfs41_session_sequence(&args->sequence, session, 0);
124 
125     if (dir == &res->root) {
126         compound_add_op(&compound, OP_PUTROOTFH, NULL, &res->putfh);
127         compound_add_op(&compound, OP_GETFH, NULL, &res->getrootfh);
128         compound_add_op(&compound, OP_GETATTR, &args->getrootattr,
129             &res->getrootattr);
130     } else {
131         args->putfh.file = dir;
132         compound_add_op(&compound, OP_PUTFH, &args->putfh, &res->putfh);
133     }
134 
135     for (i = 0; i < component_count; i++) {
136         compound_add_op(&compound, OP_LOOKUP, &args->lookup[i], &res->lookup[i]);
137         compound_add_op(&compound, OP_GETFH, NULL, &res->getfh[i]);
138         compound_add_op(&compound, OP_GETATTR, &args->getattr[i],
139             &res->getattr[i]);
140     }
141 
142     status = compound_encode_send_decode(session, &compound, TRUE);
143     if (status)
144         goto out;
145 
146     compound_error(status = compound.res.status);
147 out:
148     return status;
149 }
150 
151 static int map_lookup_error(int status, bool_t last_component)
152 {
153     switch (status) {
154     case NFS4ERR_NOENT:
155         if (last_component)     return ERROR_FILE_NOT_FOUND;
156         else                    return ERROR_PATH_NOT_FOUND;
157     case NFS4ERR_SYMLINK:       return ERROR_REPARSE;
158     case NFS4ERR_MOVED:         return ERROR_FILESYSTEM_ABSENT;
159     default: return nfs_to_windows_error(status, ERROR_FILE_NOT_FOUND);
160     }
161 }
162 
163 static int server_lookup(
164     IN nfs41_session *session,
165     IN nfs41_path_fh *dir,
166     IN const char *path,
167     IN const char *path_end,
168     IN uint32_t count,
169     IN nfs41_lookup_component_args *args,
170     IN nfs41_lookup_component_res *res,
171     OUT OPTIONAL nfs41_path_fh **parent_out,
172     OUT OPTIONAL nfs41_path_fh **target_out,
173     OUT OPTIONAL nfs41_file_info *info_out)
174 {
175     nfs41_path_fh *file, *parent;
176     uint32_t i = 0;
177     int status;
178 
179     if (parent_out) *parent_out = NULL;
180     if (target_out) *target_out = NULL;
181 
182     lookup_rpc(session, dir, count, args, res);
183 
184     status = res->sequence.sr_status;       if (status) goto out;
185     status = res->putfh.status;             if (status) goto out;
186     status = res->getrootfh.status;         if (status) goto out;
187     status = res->getrootattr.status;       if (status) goto out;
188 
189     if (dir == &res->root) {
190         nfs41_component name = { 0 };
191 
192         /* fill in the file handle's fileid and superblock */
193         dir->fh.fileid = res->getrootattr.info->fileid;
194         status = nfs41_superblock_for_fh(session,
195             &res->getrootattr.info->fsid, NULL, dir);
196         if (status)
197             goto out;
198 
199         /* get the name of the parent (empty if its the root) */
200         last_component(path, count ? args->lookup[0].name->name : path_end, &name);
201 
202         /* add the file handle and attributes to the name cache */
203         memcpy(&res->getrootattr.info->attrmask,
204             &res->getrootattr.obj_attributes.attrmask, sizeof(bitmap4));
205         nfs41_name_cache_insert(session_name_cache(session), path, &name,
206             &dir->fh, res->getrootattr.info, NULL, OPEN_DELEGATE_NONE);
207     }
208     file = dir;
209 
210     if (count == 0) {
211         if (target_out)
212             *target_out = dir;
213         if (info_out)
214             memcpy(info_out, res->getrootattr.info, sizeof(nfs41_file_info));
215     } else if (count == 1) {
216         if (parent_out)
217             *parent_out = dir;
218     }
219 
220     for (i = 0; i < count; i++) {
221         if (res->lookup[i].status == NFS4ERR_SYMLINK) {
222             /* return the symlink as the parent file */
223             last_component(path, args->lookup[i].name->name, &file->name);
224             if (parent_out) *parent_out = file;
225         } else if (res->lookup[i].status == NFS4ERR_NOENT) {
226             /* insert a negative lookup entry */
227             nfs41_name_cache_insert(session_name_cache(session), path,
228                 args->lookup[i].name, NULL, NULL, NULL, OPEN_DELEGATE_NONE);
229         }
230         status = res->lookup[i].status;     if (status) break;
231 
232         if (res->getfh[i].status == NFS4ERR_MOVED) {
233             /* save enough information to follow the referral */
234             path_fh_copy(&res->referral->parent, file);
235             res->referral->name.name = args->lookup[i].name->name;
236             res->referral->name.len = args->lookup[i].name->len;
237         }
238         status = res->getfh[i].status;      if (status) break;
239         status = res->getattr[i].status;    if (status) break;
240 
241         parent = file;
242         file = &res->file[i];
243 
244         /* fill in the file handle's fileid and superblock */
245         file->fh.fileid = res->getattr[i].info->fileid;
246         status = nfs41_superblock_for_fh(session,
247             &res->getattr[i].info->fsid, &parent->fh, file);
248         if (status)
249             break;
250 
251         /* add the file handle and attributes to the name cache */
252         memcpy(&res->getattr[i].info->attrmask,
253             &res->getattr[i].obj_attributes.attrmask, sizeof(bitmap4));
254         nfs41_name_cache_insert(session_name_cache(session),
255             path, args->lookup[i].name, &res->file[i].fh,
256             res->getattr[i].info, NULL, OPEN_DELEGATE_NONE);
257 
258         if (i == count-1) {
259             if (target_out)
260                 *target_out = file;
261             if (info_out)
262                 memcpy(info_out, res->getattr[i].info, sizeof(nfs41_file_info));
263         } else if (i == count-2) {
264             if (parent_out)
265                 *parent_out = file;
266         }
267     }
268 out:
269     return map_lookup_error(status, i == count-1);
270 }
271 
272 static uint32_t max_lookup_components(
273     IN const nfs41_session *session)
274 {
275     const uint32_t comps = (session->fore_chan_attrs.ca_maxoperations - 4) / 3;
276     return min(comps, MAX_LOOKUP_COMPONENTS);
277 }
278 
279 static uint32_t get_component_array(
280     IN OUT const char **path_pos,
281     IN const char *path_end,
282     IN uint32_t max_components,
283     OUT nfs41_path_fh *components,
284     OUT uint32_t *component_count)
285 {
286     uint32_t i;
287 
288     for (i = 0; i < max_components; i++) {
289         if (!next_component(*path_pos, path_end, &components[i].name))
290             break;
291         *path_pos = components[i].name.name + components[i].name.len;
292     }
293 
294     *component_count = i;
295     return i;
296 }
297 
298 static int server_lookup_loop(
299     IN nfs41_session *session,
300     IN OPTIONAL nfs41_path_fh *parent_in,
301     IN nfs41_abs_path *path,
302     IN const char *path_pos,
303     IN struct lookup_referral *referral,
304     OUT OPTIONAL nfs41_path_fh *parent_out,
305     OUT OPTIONAL nfs41_path_fh *target_out,
306     OUT OPTIONAL nfs41_file_info *info_out)
307 {
308     nfs41_lookup_component_args args = { 0 };
309     nfs41_lookup_component_res res = { 0 };
310     nfs41_path_fh *dir, *parent, *target;
311     const char *path_end;
312     const uint32_t max_components = max_lookup_components(session);
313     uint32_t count;
314     int status = NO_ERROR;
315 
316     init_component_args(&args, &res, path, referral);
317     parent = NULL;
318     target = NULL;
319 
320     path_end = path->path + path->len;
321     dir = parent_in ? parent_in : &res.root;
322 
323     while (get_component_array(&path_pos, path_end,
324         max_components, res.file, &count)) {
325 
326         status = server_lookup(session, dir, path->path, path_end, count,
327             &args, &res, &parent, &target, info_out);
328 
329         if (status == ERROR_REPARSE) {
330             /* copy the component name of the symlink */
331             if (parent_out && parent) {
332                 const ptrdiff_t offset = parent->name.name - path->path;
333                 parent_out->name.name = parent_out->path->path + offset;
334                 parent_out->name.len = parent->name.len;
335             }
336             goto out_parent;
337         }
338         if (status == ERROR_FILE_NOT_FOUND && is_last_component(path_pos, path_end))
339             goto out_parent;
340         if (status)
341             goto out;
342 
343         dir = target;
344     }
345 
346     if (dir == &res.root && (target_out || info_out)) {
347         /* didn't get any components, so we just need the root */
348         status = server_lookup(session, dir, path->path, path_end,
349             0, &args, &res, &parent, &target, info_out);
350         if (status)
351             goto out;
352     }
353 
354     if (target_out && target) fh_copy(&target_out->fh, &target->fh);
355 out_parent:
356     if (parent_out && parent) fh_copy(&parent_out->fh, &parent->fh);
357 out:
358     return status;
359 }
360 
361 
362 static void referral_locations_free(
363     IN fs_locations4 *locations)
364 {
365     uint32_t i;
366     if (locations->locations) {
367         for (i = 0; i < locations->location_count; i++)
368             free(locations->locations[i].servers);
369         free(locations->locations);
370     }
371 }
372 
373 static int referral_resolve(
374     IN nfs41_root *root,
375     IN nfs41_session *session_in,
376     IN struct lookup_referral *referral,
377     OUT nfs41_abs_path *path_out,
378     OUT nfs41_session **session_out)
379 {
380     char rest_of_path[NFS41_MAX_PATH_LEN];
381     fs_locations4 locations = { 0 };
382     const fs_location4 *location;
383     nfs41_client *client;
384     int status;
385 
386     /* get fs_locations */
387     status = nfs41_fs_locations(session_in, &referral->parent,
388         &referral->name, &locations);
389     if (status) {
390         eprintf("nfs41_fs_locations() failed with %s\n",
391             nfs_error_string(status));
392         status = nfs_to_windows_error(status, ERROR_PATH_NOT_FOUND);
393         goto out;
394     }
395 
396     /* mount the first location available */
397     status = nfs41_root_mount_referral(root, &locations, &location, &client);
398     if (status) {
399         eprintf("nfs41_root_mount_referral() failed with %d\n",
400             status);
401         goto out;
402     }
403 
404     /* format a new path from that location's root */
405     if (FAILED(StringCchCopyA(rest_of_path, NFS41_MAX_PATH_LEN,
406             referral->name.name + referral->name.len))) {
407         status = ERROR_FILENAME_EXCED_RANGE;
408         goto out;
409     }
410 
411     AcquireSRWLockExclusive(&path_out->lock);
412     abs_path_copy(path_out, &location->path);
413     if (FAILED(StringCchCatA(path_out->path, NFS41_MAX_PATH_LEN, rest_of_path)))
414         status = ERROR_FILENAME_EXCED_RANGE;
415     path_out->len = path_out->len + (unsigned short)strlen(rest_of_path);
416     ReleaseSRWLockExclusive(&path_out->lock);
417 
418     if (session_out) *session_out = client->session;
419 out:
420     referral_locations_free(&locations);
421     return status;
422 }
423 
424 int nfs41_lookup(
425     IN nfs41_root *root,
426     IN nfs41_session *session,
427     IN OUT nfs41_abs_path *path_inout,
428     OUT OPTIONAL nfs41_path_fh *parent_out,
429     OUT OPTIONAL nfs41_path_fh *target_out,
430     OUT OPTIONAL nfs41_file_info *info_out,
431     OUT nfs41_session **session_out)
432 {
433     nfs41_abs_path path;
434     struct nfs41_name_cache *cache = session_name_cache(session);
435     nfs41_path_fh parent, target, *server_start;
436     const char *path_pos, *path_end;
437     struct lookup_referral referral;
438     bool_t negative = 0;
439     int status;
440 
441     if (session_out) *session_out = session;
442 
443     InitializeSRWLock(&path.lock);
444 
445     /* to avoid holding this lock over multiple rpcs,
446      * make a copy of the path and use that instead */
447     AcquireSRWLockShared(&path_inout->lock);
448     abs_path_copy(&path, path_inout);
449     ReleaseSRWLockShared(&path_inout->lock);
450 
451     path_pos = path.path;
452     path_end = path.path + path.len;
453 
454     dprintf(LULVL, "--> nfs41_lookup('%s')\n", path.path);
455 
456     if (parent_out == NULL) parent_out = &parent;
457     if (target_out == NULL) target_out = &target;
458     parent_out->fh.len = target_out->fh.len = 0;
459 
460     status = nfs41_name_cache_lookup(cache, path_pos, path_end, &path_pos,
461         &parent_out->fh, &target_out->fh, info_out, &negative);
462     if (status == NO_ERROR || negative)
463         goto out;
464 
465     if (parent_out->fh.len) {
466         /* start where the name cache left off */
467         if (&parent != parent_out) {
468             /* must make a copy for server_start, because
469              * server_lookup_loop() will overwrite parent_out */
470             path_fh_copy(&parent, parent_out);
471         }
472         server_start = &parent;
473     } else {
474         /* start with PUTROOTFH */
475         server_start = NULL;
476     }
477 
478     status = server_lookup_loop(session, server_start,
479         &path, path_pos, &referral, parent_out, target_out, info_out);
480 
481     if (status == ERROR_FILESYSTEM_ABSENT) {
482         nfs41_session *new_session;
483 
484         /* create a session to the referred server and
485          * reformat the path relative to that server's root */
486         status = referral_resolve(root, session,
487             &referral, path_inout, &new_session);
488         if (status) {
489             eprintf("referral_resolve() failed with %d\n", status);
490             goto out;
491         }
492 
493         /* update the positions of the parent and target components */
494         last_component(path_inout->path, path_inout->path + path_inout->len,
495             &target_out->name);
496         last_component(path_inout->path, target_out->name.name,
497             &parent_out->name);
498 
499         if (session_out) *session_out = new_session;
500 
501         /* look up the new path */
502         status = nfs41_lookup(root, new_session, path_inout,
503             parent_out, target_out, info_out, session_out);
504     }
505 out:
506     dprintf(LULVL, "<-- nfs41_lookup() returning %d\n", status);
507     return status;
508 }
509