[NFSD]
[reactos.git] / reactos / base / services / nfsd / lookup.c
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 }