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
25 #include "from_kernel.h"
26 #include "nfs41_ops.h"
27 #include "daemon_debug.h"
32 typedef union _FILE_DIR_INFO_UNION
{
33 ULONG NextEntryOffset
;
34 FILE_NAMES_INFORMATION fni
;
35 FILE_DIRECTORY_INFO fdi
;
36 FILE_FULL_DIR_INFO ffdi
;
37 FILE_ID_FULL_DIR_INFO fifdi
;
38 FILE_BOTH_DIR_INFORMATION fbdi
;
39 FILE_ID_BOTH_DIR_INFO fibdi
;
40 } FILE_DIR_INFO_UNION
, *PFILE_DIR_INFO_UNION
;
44 static int parse_readdir(unsigned char *buffer
, uint32_t length
, nfs41_upcall
*upcall
)
47 readdir_upcall_args
*args
= &upcall
->args
.readdir
;
49 status
= safe_read(&buffer
, &length
, &args
->query_class
, sizeof(args
->query_class
));
51 status
= safe_read(&buffer
, &length
, &args
->buf_len
, sizeof(args
->buf_len
));
53 status
= get_name(&buffer
, &length
, &args
->filter
);
55 status
= safe_read(&buffer
, &length
, &args
->initial
, sizeof(args
->initial
));
57 status
= safe_read(&buffer
, &length
, &args
->restart
, sizeof(args
->restart
));
59 status
= safe_read(&buffer
, &length
, &args
->single
, sizeof(args
->single
));
61 status
= safe_read(&buffer
, &length
, &args
->kbuf
, sizeof(args
->kbuf
));
63 args
->root
= upcall
->root_ref
;
64 args
->state
= upcall
->state_ref
;
66 dprintf(1, "parsing NFS41_DIR_QUERY: info_class=%d buf_len=%d "
67 "filter='%s'\n\tInitial\\Restart\\Single %d\\%d\\%d buf=%p\n",
68 args
->query_class
, args
->buf_len
, args
->filter
,
69 args
->initial
, args
->restart
, args
->single
, args
->kbuf
);
74 #define FILTER_STAR '*'
77 static __inline
const char* skip_stars(
80 while (*filter
== FILTER_STAR
)
85 static int readdir_filter(
89 const char *f
= filter
, *n
= name
;
92 if (*f
== FILTER_STAR
) {
96 while (*n
&& !readdir_filter(f
, n
))
98 } else if (*f
== FILTER_QM
|| *f
== *n
) {
104 return *f
== *n
|| *skip_stars(f
) == '\0';
107 static uint32_t readdir_size_for_entry(
109 IN
uint32_t wname_size
)
111 uint32_t needed
= wname_size
;
114 case FileDirectoryInformation
:
115 needed
+= FIELD_OFFSET(FILE_DIRECTORY_INFO
, FileName
);
117 case FileIdFullDirectoryInformation
:
118 needed
+= FIELD_OFFSET(FILE_ID_FULL_DIR_INFO
, FileName
);
120 case FileFullDirectoryInformation
:
121 needed
+= FIELD_OFFSET(FILE_FULL_DIR_INFO
, FileName
);
123 case FileIdBothDirectoryInformation
:
124 needed
+= FIELD_OFFSET(FILE_ID_BOTH_DIR_INFO
, FileName
);
126 case FileBothDirectoryInformation
:
127 needed
+= FIELD_OFFSET(FILE_BOTH_DIR_INFORMATION
, FileName
);
129 case FileNamesInformation
:
130 needed
+= FIELD_OFFSET(FILE_NAMES_INFORMATION
, FileName
);
133 eprintf("unhandled dir query class %d\n", query_class
);
139 static void readdir_copy_dir_info(
140 IN nfs41_readdir_entry
*entry
,
141 IN PFILE_DIR_INFO_UNION info
)
143 info
->fdi
.FileIndex
= (ULONG
)entry
->attr_info
.fileid
;
144 nfs_time_to_file_time(&entry
->attr_info
.time_create
,
145 &info
->fdi
.CreationTime
);
146 nfs_time_to_file_time(&entry
->attr_info
.time_access
,
147 &info
->fdi
.LastAccessTime
);
148 nfs_time_to_file_time(&entry
->attr_info
.time_modify
,
149 &info
->fdi
.LastWriteTime
);
150 /* XXX: was using 'change' attr, but that wasn't giving a time */
151 nfs_time_to_file_time(&entry
->attr_info
.time_modify
,
152 &info
->fdi
.ChangeTime
);
153 info
->fdi
.EndOfFile
.QuadPart
=
154 info
->fdi
.AllocationSize
.QuadPart
=
155 entry
->attr_info
.size
;
156 info
->fdi
.FileAttributes
= nfs_file_info_to_attributes(
160 static void readdir_copy_shortname(
163 OUT CCHAR
*name_size_out
)
165 /* GetShortPathName returns number of characters, not including \0 */
166 *name_size_out
= (CCHAR
)GetShortPathNameW(name
, name_out
, 12);
167 if (*name_size_out
) {
169 *name_size_out
*= sizeof(WCHAR
);
173 static void readdir_copy_full_dir_info(
174 IN nfs41_readdir_entry
*entry
,
175 IN PFILE_DIR_INFO_UNION info
)
177 readdir_copy_dir_info(entry
, info
);
178 /* for files with the FILE_ATTRIBUTE_REPARSE_POINT attribute,
179 * EaSize is used instead to specify its reparse tag. this makes
180 * the 'dir' command to show files as <SYMLINK>, and triggers a
181 * FSCTL_GET_REPARSE_POINT to query the symlink target
183 info
->fifdi
.EaSize
= entry
->attr_info
.type
== NF4LNK
?
184 IO_REPARSE_TAG_SYMLINK
: 0;
187 static void readdir_copy_both_dir_info(
188 IN nfs41_readdir_entry
*entry
,
190 IN PFILE_DIR_INFO_UNION info
)
192 readdir_copy_full_dir_info(entry
, info
);
193 readdir_copy_shortname(wname
, info
->fbdi
.ShortName
,
194 &info
->fbdi
.ShortNameLength
);
197 static void readdir_copy_filename(
199 IN
uint32_t name_size
,
201 OUT ULONG
*name_size_out
)
203 *name_size_out
= name_size
;
204 memcpy(name_out
, name
, name_size
);
207 static int format_abs_path(
208 IN
const nfs41_abs_path
*path
,
209 IN
const nfs41_component
*name
,
210 OUT nfs41_abs_path
*path_out
)
212 /* format an absolute path 'parent\name' */
213 int status
= NO_ERROR
;
215 InitializeSRWLock(&path_out
->lock
);
216 abs_path_copy(path_out
, path
);
217 if (FAILED(StringCchPrintfA(path_out
->path
+ path_out
->len
,
218 NFS41_MAX_PATH_LEN
- path_out
->len
, "\\%s", name
->name
))) {
219 status
= ERROR_FILENAME_EXCED_RANGE
;
222 path_out
->len
+= name
->len
+ 1;
227 static int lookup_entry(
229 IN nfs41_session
*session
,
230 IN nfs41_path_fh
*parent
,
231 OUT nfs41_readdir_entry
*entry
)
234 nfs41_component name
;
237 name
.name
= entry
->name
;
238 name
.len
= (unsigned short)entry
->name_len
- 1;
240 status
= format_abs_path(parent
->path
, &name
, &path
);
241 if (status
) goto out
;
243 status
= nfs41_lookup(root
, session
, &path
,
244 NULL
, NULL
, &entry
->attr_info
, NULL
);
245 if (status
) goto out
;
250 static int lookup_symlink(
252 IN nfs41_session
*session
,
253 IN nfs41_path_fh
*parent
,
254 IN
const nfs41_component
*name
,
255 OUT nfs41_file_info
*info_out
)
259 nfs41_file_info info
;
262 status
= format_abs_path(parent
->path
, name
, &path
);
263 if (status
) goto out
;
266 status
= nfs41_lookup(root
, session
, &path
, NULL
, &file
, &info
, &session
);
267 if (status
) goto out
;
269 last_component(path
.path
, path
.path
+ path
.len
, &file
.name
);
271 status
= nfs41_symlink_follow(root
, session
, &file
, &info
);
272 if (status
) goto out
;
274 info_out
->symlink_dir
= info
.type
== NF4DIR
;
279 static int readdir_copy_entry(
280 IN readdir_upcall_args
*args
,
281 IN nfs41_readdir_entry
*entry
,
282 IN OUT
unsigned char **dst_pos
,
283 IN OUT
uint32_t *dst_len
)
286 WCHAR wname
[NFS4_OPAQUE_LIMIT
];
287 uint32_t wname_len
, wname_size
, needed
;
288 PFILE_DIR_INFO_UNION info
;
290 wname_len
= MultiByteToWideChar(CP_UTF8
, 0,
291 entry
->name
, entry
->name_len
, wname
, NFS4_OPAQUE_LIMIT
);
292 wname_size
= (wname_len
- 1) * sizeof(WCHAR
);
294 needed
= readdir_size_for_entry(args
->query_class
, wname_size
);
295 if (!needed
|| needed
> *dst_len
) {
300 info
= (PFILE_DIR_INFO_UNION
)*dst_pos
;
301 info
->NextEntryOffset
= align8(needed
);
302 *dst_pos
+= info
->NextEntryOffset
;
303 *dst_len
-= info
->NextEntryOffset
;
305 if (entry
->attr_info
.rdattr_error
== NFS4ERR_MOVED
) {
306 entry
->attr_info
.type
= NF4DIR
; /* default to dir */
307 /* look up attributes for referral entries, but ignore return value;
308 * it's okay if lookup fails, we'll just write garbage attributes */
309 lookup_entry(args
->root
, args
->state
->session
,
310 &args
->state
->file
, entry
);
311 } else if (entry
->attr_info
.type
== NF4LNK
) {
312 nfs41_component name
;
313 name
.name
= entry
->name
;
314 name
.len
= (unsigned short)entry
->name_len
- 1;
315 /* look up the symlink target to see whether it's a directory */
316 lookup_symlink(args
->root
, args
->state
->session
,
317 &args
->state
->file
, &name
, &entry
->attr_info
);
320 switch (args
->query_class
)
322 case FileNamesInformation
:
323 info
->fni
.FileIndex
= 0;
324 readdir_copy_filename(wname
, wname_size
,
325 info
->fni
.FileName
, &info
->fni
.FileNameLength
);
327 case FileDirectoryInformation
:
328 readdir_copy_dir_info(entry
, info
);
329 readdir_copy_filename(wname
, wname_size
,
330 info
->fdi
.FileName
, &info
->fdi
.FileNameLength
);
332 case FileFullDirectoryInformation
:
333 readdir_copy_full_dir_info(entry
, info
);
334 readdir_copy_filename(wname
, wname_size
,
335 info
->ffdi
.FileName
, &info
->ffdi
.FileNameLength
);
337 case FileIdFullDirectoryInformation
:
338 readdir_copy_full_dir_info(entry
, info
);
339 info
->fibdi
.FileId
.QuadPart
= (LONGLONG
)entry
->attr_info
.fileid
;
340 readdir_copy_filename(wname
, wname_size
,
341 info
->fifdi
.FileName
, &info
->fifdi
.FileNameLength
);
343 case FileBothDirectoryInformation
:
344 readdir_copy_both_dir_info(entry
, wname
, info
);
345 readdir_copy_filename(wname
, wname_size
,
346 info
->fbdi
.FileName
, &info
->fbdi
.FileNameLength
);
348 case FileIdBothDirectoryInformation
:
349 readdir_copy_both_dir_info(entry
, wname
, info
);
350 info
->fibdi
.FileId
.QuadPart
= (LONGLONG
)entry
->attr_info
.fileid
;
351 readdir_copy_filename(wname
, wname_size
,
352 info
->fibdi
.FileName
, &info
->fibdi
.FileNameLength
);
355 eprintf("unhandled dir query class %d\n", args
->query_class
);
363 #define COOKIE_DOT ((uint64_t)-2)
364 #define COOKIE_DOTDOT ((uint64_t)-1)
366 static int readdir_add_dots(
367 IN readdir_upcall_args
*args
,
368 IN OUT
unsigned char *entry_buf
,
369 IN
uint32_t entry_buf_len
,
370 OUT
uint32_t *len_out
,
371 OUT
uint32_t **last_offset
)
374 const uint32_t entry_len
= (uint32_t)FIELD_OFFSET(nfs41_readdir_entry
, name
);
375 nfs41_readdir_entry
*entry
;
376 nfs41_open_state
*state
= args
->state
;
380 switch (state
->cookie
.cookie
) {
382 if (entry_buf_len
< entry_len
+ 2) {
383 status
= ERROR_BUFFER_OVERFLOW
;
384 dprintf(1, "not enough room for '.' entry. received %d need %d\n",
385 entry_buf_len
, entry_len
+ 2);
386 args
->query_reply_len
= entry_len
+ 2;
390 entry
= (nfs41_readdir_entry
*)entry_buf
;
391 ZeroMemory(&entry
->attr_info
, sizeof(nfs41_file_info
));
393 status
= nfs41_cached_getattr(state
->session
,
394 &state
->file
, &entry
->attr_info
);
396 dprintf(1, "failed to add '.' entry.\n");
399 entry
->cookie
= COOKIE_DOT
;
401 StringCbCopyA(entry
->name
, entry
->name_len
, ".");
402 entry
->next_entry_offset
= entry_len
+ entry
->name_len
;
404 entry_buf
+= entry
->next_entry_offset
;
405 entry_buf_len
-= entry
->next_entry_offset
;
406 *len_out
+= entry
->next_entry_offset
;
407 *last_offset
= &entry
->next_entry_offset
;
412 if (entry_buf_len
< entry_len
+ 3) {
413 status
= ERROR_BUFFER_OVERFLOW
;
414 dprintf(1, "not enough room for '..' entry. received %d need %d\n",
415 entry_buf_len
, entry_len
);
416 args
->query_reply_len
= entry_len
+ 2;
419 /* XXX: this skips '..' when listing root fh */
420 if (state
->file
.name
.len
== 0)
423 entry
= (nfs41_readdir_entry
*)entry_buf
;
424 ZeroMemory(&entry
->attr_info
, sizeof(nfs41_file_info
));
426 status
= nfs41_cached_getattr(state
->session
,
427 &state
->parent
, &entry
->attr_info
);
429 status
= ERROR_FILE_NOT_FOUND
;
430 dprintf(1, "failed to add '..' entry.\n");
433 entry
->cookie
= COOKIE_DOTDOT
;
435 StringCbCopyA(entry
->name
, entry
->name_len
, "..");
436 entry
->next_entry_offset
= entry_len
+ entry
->name_len
;
438 entry_buf
+= entry
->next_entry_offset
;
439 entry_buf_len
-= entry
->next_entry_offset
;
440 *len_out
+= entry
->next_entry_offset
;
441 *last_offset
= &entry
->next_entry_offset
;
444 if (state
->cookie
.cookie
== COOKIE_DOTDOT
||
445 state
->cookie
.cookie
== COOKIE_DOT
)
446 ZeroMemory(&state
->cookie
, sizeof(nfs41_readdir_cookie
));
451 static int handle_readdir(nfs41_upcall
*upcall
)
454 readdir_upcall_args
*args
= &upcall
->args
.readdir
;
455 nfs41_open_state
*state
= upcall
->state_ref
;
456 unsigned char *entry_buf
= NULL
;
457 uint32_t entry_buf_len
;
458 bitmap4 attr_request
;
460 /* make sure we allocate enough space for one nfs41_readdir_entry */
461 const uint32_t max_buf_len
= max(args
->buf_len
,
462 sizeof(nfs41_readdir_entry
) + NFS41_MAX_COMPONENT_LEN
);
464 dprintf(1, "-> handle_nfs41_dirquery(%s,%d,%d,%d)\n",
465 args
->filter
, args
->initial
, args
->restart
, args
->single
);
467 args
->query_reply_len
= 0;
469 if (args
->initial
|| args
->restart
) {
470 ZeroMemory(&state
->cookie
, sizeof(nfs41_readdir_cookie
));
471 if (!state
->cookie
.cookie
)
472 dprintf(1, "initializing the 1st readdir cookie\n");
473 else if (args
->restart
)
474 dprintf(1, "restarting; clearing previous cookie %llu\n",
475 state
->cookie
.cookie
);
476 else if (args
->initial
)
477 dprintf(1, "*** initial; clearing previous cookie %llu!\n",
478 state
->cookie
.cookie
);
479 } else if (!state
->cookie
.cookie
) {
480 dprintf(1, "handle_nfs41_readdir: EOF\n");
481 status
= ERROR_NO_MORE_FILES
;
485 entry_buf
= calloc(max_buf_len
, sizeof(unsigned char));
486 if (entry_buf
== NULL
) {
487 status
= GetLastError();
488 goto out_free_cookie
;
491 entry_buf_len
= max_buf_len
;
493 nfs41_superblock_getattr_mask(state
->file
.fh
.superblock
, &attr_request
);
494 attr_request
.arr
[0] |= FATTR4_WORD0_RDATTR_ERROR
;
496 if (strchr(args
->filter
, FILTER_STAR
) || strchr(args
->filter
, FILTER_QM
)) {
497 /* use READDIR for wildcards */
499 uint32_t dots_len
= 0;
500 uint32_t *dots_next_offset
= NULL
;
502 if (args
->filter
[0] == '*' && args
->filter
[1] == '\0') {
503 status
= readdir_add_dots(args
, entry_buf
,
504 entry_buf_len
, &dots_len
, &dots_next_offset
);
506 goto out_free_cookie
;
507 entry_buf_len
-= dots_len
;
510 if (dots_len
&& args
->single
) {
511 dprintf(2, "skipping nfs41_readdir because the single query "
512 "will use . or ..\n");
516 dprintf(2, "calling nfs41_readdir with cookie %llu\n",
517 state
->cookie
.cookie
);
518 status
= nfs41_readdir(state
->session
, &state
->file
,
519 &attr_request
, &state
->cookie
, entry_buf
+ dots_len
,
520 &entry_buf_len
, &eof
);
522 dprintf(1, "nfs41_readdir failed with %s\n",
523 nfs_error_string(status
));
524 status
= nfs_to_windows_error(status
, ERROR_BAD_NET_RESP
);
525 goto out_free_cookie
;
529 if (!entry_buf_len
&& dots_next_offset
)
530 *dots_next_offset
= 0;
531 entry_buf_len
+= dots_len
;
533 /* use LOOKUP for single files */
534 nfs41_readdir_entry
*entry
= (nfs41_readdir_entry
*)entry_buf
;
536 entry
->name_len
= (uint32_t)strlen(args
->filter
) + 1;
537 StringCbCopyA(entry
->name
, entry
->name_len
, args
->filter
);
538 entry
->next_entry_offset
= 0;
540 status
= lookup_entry(upcall
->root_ref
,
541 state
->session
, &state
->file
, entry
);
543 dprintf(1, "single_lookup failed with %d\n", status
);
544 goto out_free_cookie
;
546 entry_buf_len
= entry
->name_len
+
547 FIELD_OFFSET(nfs41_readdir_entry
, name
);
552 status
= args
->initial
? ERROR_FILE_NOT_FOUND
: ERROR_NO_MORE_FILES
;
555 unsigned char *entry_pos
= entry_buf
;
556 unsigned char *dst_pos
= args
->kbuf
;
557 uint32_t dst_len
= args
->buf_len
;
558 nfs41_readdir_entry
*entry
;
559 PULONG offset
, last_offset
= NULL
;
562 entry
= (nfs41_readdir_entry
*)entry_pos
;
563 offset
= (PULONG
)dst_pos
; /* ULONG NextEntryOffset */
565 dprintf(2, "filter %s looking at %s with cookie %d\n",
566 args
->filter
, entry
->name
, entry
->cookie
);
567 if (readdir_filter((const char*)args
->filter
, entry
->name
)) {
568 if (readdir_copy_entry(args
, entry
, &dst_pos
, &dst_len
)) {
570 dprintf(2, "not enough space to copy entry %s (cookie %d)\n",
571 entry
->name
, entry
->cookie
);
574 last_offset
= offset
;
577 state
->cookie
.cookie
= entry
->cookie
;
579 /* last entry we got from the server */
580 if (!entry
->next_entry_offset
)
583 /* we found our single entry, but the server has more */
584 if (args
->single
&& last_offset
) {
588 entry_pos
+= entry
->next_entry_offset
;
590 args
->query_reply_len
= args
->buf_len
- dst_len
;
594 dprintf(1, "no entries matched; fetch more\n");
600 dprintf(1, "we don't need to save a cookie\n");
601 goto out_free_cookie
;
603 dprintf(1, "saving cookie %llu\n", state
->cookie
.cookie
);
608 dprintf(1, "<- handle_nfs41_dirquery(%s,%d,%d,%d) returning ",
609 args
->filter
, args
->initial
, args
->restart
, args
->single
);
612 case ERROR_FILE_NOT_FOUND
:
613 dprintf(1, "ERROR_FILE_NOT_FOUND.\n");
615 case ERROR_NO_MORE_FILES
:
616 dprintf(1, "ERROR_NO_MORE_FILES.\n");
618 case ERROR_BUFFER_OVERFLOW
:
619 upcall
->last_error
= status
;
620 status
= ERROR_SUCCESS
;
623 dprintf(1, "error code %d.\n", status
);
627 dprintf(1, "success!\n");
631 state
->cookie
.cookie
= 0;
635 static int marshall_readdir(unsigned char *buffer
, uint32_t *length
, nfs41_upcall
*upcall
)
638 readdir_upcall_args
*args
= &upcall
->args
.readdir
;
640 status
= safe_write(&buffer
, length
, &args
->query_reply_len
, sizeof(args
->query_reply_len
));
645 const nfs41_upcall_op nfs41_op_readdir
= {