1 /* NFSv4.1 client for Windows
2 * Copyright © 2012 The Regents of the University of Michigan
4 * Olga Kornievskaia <aglo@umich.edu>
5 * Casey Bodley <cbodley@umich.edu>
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.
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.
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
26 #include "nfs41_compound.h"
27 #include "nfs41_ops.h"
28 #include "name_cache.h"
30 #include "daemon_debug.h"
33 #define LULVL 2 /* dprintf level for lookup logging */
36 #define MAX_LOOKUP_COMPONENTS 8
38 /* map NFS4ERR_MOVED to an arbitrary windows error */
39 #define ERROR_FILESYSTEM_ABSENT ERROR_DEVICE_REMOVED
41 struct lookup_referral
{
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
];
54 } nfs41_lookup_component_args
;
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
];
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
;
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
)
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
;
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
;
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
;
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
)
116 nfs41_compound compound
;
117 nfs_argop4 argops
[4+MAX_LOOKUP_COMPONENTS
*3];
118 nfs_resop4 resops
[4+MAX_LOOKUP_COMPONENTS
*3];
120 compound_init(&compound
, argops
, resops
, "lookup");
122 compound_add_op(&compound
, OP_SEQUENCE
, &args
->sequence
, &res
->sequence
);
123 nfs41_session_sequence(&args
->sequence
, session
, 0);
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
,
131 args
->putfh
.file
= dir
;
132 compound_add_op(&compound
, OP_PUTFH
, &args
->putfh
, &res
->putfh
);
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
],
142 status
= compound_encode_send_decode(session
, &compound
, TRUE
);
146 compound_error(status
= compound
.res
.status
);
151 static int map_lookup_error(int status
, bool_t last_component
)
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
);
163 static int server_lookup(
164 IN nfs41_session
*session
,
165 IN nfs41_path_fh
*dir
,
167 IN
const char *path_end
,
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
)
175 nfs41_path_fh
*file
, *parent
;
179 if (parent_out
) *parent_out
= NULL
;
180 if (target_out
) *target_out
= NULL
;
182 lookup_rpc(session
, dir
, count
, args
, res
);
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
;
189 if (dir
== &res
->root
) {
190 nfs41_component name
= { 0 };
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
);
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
);
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
);
214 memcpy(info_out
, res
->getrootattr
.info
, sizeof(nfs41_file_info
));
215 } else if (count
== 1) {
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
);
230 status
= res
->lookup
[i
].status
; if (status
) break;
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
;
238 status
= res
->getfh
[i
].status
; if (status
) break;
239 status
= res
->getattr
[i
].status
; if (status
) break;
242 file
= &res
->file
[i
];
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
);
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
);
262 memcpy(info_out
, res
->getattr
[i
].info
, sizeof(nfs41_file_info
));
263 } else if (i
== count
-2) {
269 return map_lookup_error(status
, i
== count
-1);
272 static uint32_t max_lookup_components(
273 IN
const nfs41_session
*session
)
275 const uint32_t comps
= (session
->fore_chan_attrs
.ca_maxoperations
- 4) / 3;
276 return min(comps
, MAX_LOOKUP_COMPONENTS
);
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
)
288 for (i
= 0; i
< max_components
; i
++) {
289 if (!next_component(*path_pos
, path_end
, &components
[i
].name
))
291 *path_pos
= components
[i
].name
.name
+ components
[i
].name
.len
;
294 *component_count
= i
;
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
)
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
);
314 int status
= NO_ERROR
;
316 init_component_args(&args
, &res
, path
, referral
);
320 path_end
= path
->path
+ path
->len
;
321 dir
= parent_in
? parent_in
: &res
.root
;
323 while (get_component_array(&path_pos
, path_end
,
324 max_components
, res
.file
, &count
)) {
326 status
= server_lookup(session
, dir
, path
->path
, path_end
, count
,
327 &args
, &res
, &parent
, &target
, info_out
);
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
;
338 if (status
== ERROR_FILE_NOT_FOUND
&& is_last_component(path_pos
, path_end
))
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
);
354 if (target_out
&& target
) fh_copy(&target_out
->fh
, &target
->fh
);
356 if (parent_out
&& parent
) fh_copy(&parent_out
->fh
, &parent
->fh
);
362 static void referral_locations_free(
363 IN fs_locations4
*locations
)
366 if (locations
->locations
) {
367 for (i
= 0; i
< locations
->location_count
; i
++)
368 free(locations
->locations
[i
].servers
);
369 free(locations
->locations
);
373 static int referral_resolve(
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
)
380 char rest_of_path
[NFS41_MAX_PATH_LEN
];
381 fs_locations4 locations
= { 0 };
382 const fs_location4
*location
;
383 nfs41_client
*client
;
386 /* get fs_locations */
387 status
= nfs41_fs_locations(session_in
, &referral
->parent
,
388 &referral
->name
, &locations
);
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
);
396 /* mount the first location available */
397 status
= nfs41_root_mount_referral(root
, &locations
, &location
, &client
);
399 eprintf("nfs41_root_mount_referral() failed with %d\n",
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
;
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
);
418 if (session_out
) *session_out
= client
->session
;
420 referral_locations_free(&locations
);
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
)
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
;
441 if (session_out
) *session_out
= session
;
443 InitializeSRWLock(&path
.lock
);
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
);
451 path_pos
= path
.path
;
452 path_end
= path
.path
+ path
.len
;
454 dprintf(LULVL
, "--> nfs41_lookup('%s')\n", path
.path
);
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;
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
)
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
);
472 server_start
= &parent
;
474 /* start with PUTROOTFH */
478 status
= server_lookup_loop(session
, server_start
,
479 &path
, path_pos
, &referral
, parent_out
, target_out
, info_out
);
481 if (status
== ERROR_FILESYSTEM_ABSENT
) {
482 nfs41_session
*new_session
;
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
);
489 eprintf("referral_resolve() failed with %d\n", status
);
493 /* update the positions of the parent and target components */
494 last_component(path_inout
->path
, path_inout
->path
+ path_inout
->len
,
496 last_component(path_inout
->path
, target_out
->name
.name
,
499 if (session_out
) *session_out
= new_session
;
501 /* look up the new path */
502 status
= nfs41_lookup(root
, new_session
, path_inout
,
503 parent_out
, target_out
, info_out
, session_out
);
506 dprintf(LULVL
, "<-- nfs41_lookup() returning %d\n", status
);