xref: /reactos/base/services/nfsd/readdir.c (revision c2c66aff)
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 <stdlib.h>
25 #include "from_kernel.h"
26 #include "nfs41_ops.h"
27 #include "daemon_debug.h"
28 #include "upcall.h"
29 #include "util.h"
30 
31 
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;
41 
42 
43 /* NFS41_DIR_QUERY */
44 static int parse_readdir(unsigned char *buffer, uint32_t length, nfs41_upcall *upcall)
45 {
46     int status;
47     readdir_upcall_args *args = &upcall->args.readdir;
48 
49     status = safe_read(&buffer, &length, &args->query_class, sizeof(args->query_class));
50     if (status) goto out;
51     status = safe_read(&buffer, &length, &args->buf_len, sizeof(args->buf_len));
52     if (status) goto out;
53     status = get_name(&buffer, &length, &args->filter);
54     if (status) goto out;
55     status = safe_read(&buffer, &length, &args->initial, sizeof(args->initial));
56     if (status) goto out;
57     status = safe_read(&buffer, &length, &args->restart, sizeof(args->restart));
58     if (status) goto out;
59     status = safe_read(&buffer, &length, &args->single, sizeof(args->single));
60     if (status) goto out;
61     status = safe_read(&buffer, &length, &args->kbuf, sizeof(args->kbuf));
62     if (status) goto out;
63     args->root = upcall->root_ref;
64     args->state = upcall->state_ref;
65 
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);
70 out:
71     return status;
72 }
73 
74 #define FILTER_STAR '*'
75 #define FILTER_QM   '>'
76 
77 static __inline const char* skip_stars(
78     const char *filter)
79 {
80     while (*filter == FILTER_STAR)
81         filter++;
82     return filter;
83 }
84 
85 static int readdir_filter(
86     const char *filter,
87     const char *name)
88 {
89     const char *f = filter, *n = name;
90 
91     while (*f && *n) {
92         if (*f == FILTER_STAR) {
93             f = skip_stars(f);
94             if (*f == '\0')
95                 return 1;
96             while (*n && !readdir_filter(f, n))
97                 n++;
98         } else if (*f == FILTER_QM || *f == *n) {
99             f++;
100             n++;
101         } else
102             return 0;
103     }
104     return *f == *n || *skip_stars(f) == '\0';
105 }
106 
107 static uint32_t readdir_size_for_entry(
108     IN int query_class,
109     IN uint32_t wname_size)
110 {
111     uint32_t needed = wname_size;
112     switch (query_class)
113     {
114     case FileDirectoryInformation:
115         needed += FIELD_OFFSET(FILE_DIRECTORY_INFO, FileName);
116         break;
117     case FileIdFullDirectoryInformation:
118         needed += FIELD_OFFSET(FILE_ID_FULL_DIR_INFO, FileName);
119         break;
120     case FileFullDirectoryInformation:
121         needed += FIELD_OFFSET(FILE_FULL_DIR_INFO, FileName);
122         break;
123     case FileIdBothDirectoryInformation:
124         needed += FIELD_OFFSET(FILE_ID_BOTH_DIR_INFO, FileName);
125         break;
126     case FileBothDirectoryInformation:
127         needed += FIELD_OFFSET(FILE_BOTH_DIR_INFORMATION, FileName);
128         break;
129     case FileNamesInformation:
130         needed += FIELD_OFFSET(FILE_NAMES_INFORMATION, FileName);
131         break;
132     default:
133         eprintf("unhandled dir query class %d\n", query_class);
134         return 0;
135     }
136     return needed;
137 }
138 
139 static void readdir_copy_dir_info(
140     IN nfs41_readdir_entry *entry,
141     IN PFILE_DIR_INFO_UNION info)
142 {
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(
157         &entry->attr_info);
158 }
159 
160 static void readdir_copy_shortname(
161     IN LPCWSTR name,
162     OUT LPWSTR name_out,
163     OUT CCHAR *name_size_out)
164 {
165     /* GetShortPathName returns number of characters, not including \0 */
166     *name_size_out = (CCHAR)GetShortPathNameW(name, name_out, 12);
167     if (*name_size_out) {
168         *name_size_out++;
169         *name_size_out *= sizeof(WCHAR);
170     }
171 }
172 
173 static void readdir_copy_full_dir_info(
174     IN nfs41_readdir_entry *entry,
175     IN PFILE_DIR_INFO_UNION info)
176 {
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
182      */
183     info->fifdi.EaSize = entry->attr_info.type == NF4LNK ?
184         IO_REPARSE_TAG_SYMLINK : 0;
185 }
186 
187 static void readdir_copy_both_dir_info(
188     IN nfs41_readdir_entry *entry,
189     IN LPWSTR wname,
190     IN PFILE_DIR_INFO_UNION info)
191 {
192     readdir_copy_full_dir_info(entry, info);
193     readdir_copy_shortname(wname, info->fbdi.ShortName,
194         &info->fbdi.ShortNameLength);
195 }
196 
197 static void readdir_copy_filename(
198     IN LPCWSTR name,
199     IN uint32_t name_size,
200     OUT LPWSTR name_out,
201     OUT ULONG *name_size_out)
202 {
203     *name_size_out = name_size;
204     memcpy(name_out, name, name_size);
205 }
206 
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)
211 {
212     /* format an absolute path 'parent\name' */
213     int status = NO_ERROR;
214 
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;
220         goto out;
221     }
222     path_out->len += name->len + 1;
223 out:
224     return status;
225 }
226 
227 static int lookup_entry(
228     IN nfs41_root *root,
229     IN nfs41_session *session,
230     IN nfs41_path_fh *parent,
231     OUT nfs41_readdir_entry *entry)
232 {
233     nfs41_abs_path path;
234     nfs41_component name;
235     int status;
236 
237     name.name = entry->name;
238     name.len = (unsigned short)entry->name_len - 1;
239 
240     status = format_abs_path(parent->path, &name, &path);
241     if (status) goto out;
242 
243     status = nfs41_lookup(root, session, &path,
244         NULL, NULL, &entry->attr_info, NULL);
245     if (status) goto out;
246 out:
247     return status;
248 }
249 
250 static int lookup_symlink(
251     IN nfs41_root *root,
252     IN nfs41_session *session,
253     IN nfs41_path_fh *parent,
254     IN const nfs41_component *name,
255     OUT nfs41_file_info *info_out)
256 {
257     nfs41_abs_path path;
258     nfs41_path_fh file;
259     nfs41_file_info info;
260     int status;
261 
262     status = format_abs_path(parent->path, name, &path);
263     if (status) goto out;
264 
265     file.path = &path;
266     status = nfs41_lookup(root, session, &path, NULL, &file, &info, &session);
267     if (status) goto out;
268 
269     last_component(path.path, path.path + path.len, &file.name);
270 
271     status = nfs41_symlink_follow(root, session, &file, &info);
272     if (status) goto out;
273 
274     info_out->symlink_dir = info.type == NF4DIR;
275 out:
276     return status;
277 }
278 
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)
284 {
285     int status = 0;
286     WCHAR wname[NFS4_OPAQUE_LIMIT];
287     uint32_t wname_len, wname_size, needed;
288     PFILE_DIR_INFO_UNION info;
289 
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);
293 
294     needed = readdir_size_for_entry(args->query_class, wname_size);
295     if (!needed || needed > *dst_len) {
296         status = -1;
297         goto out;
298     }
299 
300     info = (PFILE_DIR_INFO_UNION)*dst_pos;
301     info->NextEntryOffset = align8(needed);
302     *dst_pos += info->NextEntryOffset;
303     *dst_len -= info->NextEntryOffset;
304 
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);
318     }
319 
320     switch (args->query_class)
321     {
322     case FileNamesInformation:
323         info->fni.FileIndex = 0;
324         readdir_copy_filename(wname, wname_size,
325             info->fni.FileName, &info->fni.FileNameLength);
326         break;
327     case FileDirectoryInformation:
328         readdir_copy_dir_info(entry, info);
329         readdir_copy_filename(wname, wname_size,
330             info->fdi.FileName, &info->fdi.FileNameLength);
331         break;
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);
336         break;
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);
342         break;
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);
347         break;
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);
353         break;
354     default:
355         eprintf("unhandled dir query class %d\n", args->query_class);
356         status = -1;
357         break;
358     }
359 out:
360     return status;
361 }
362 
363 #define COOKIE_DOT      ((uint64_t)-2)
364 #define COOKIE_DOTDOT   ((uint64_t)-1)
365 
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)
372 {
373     int status = 0;
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;
377 
378     *len_out = 0;
379     *last_offset = NULL;
380     switch (state->cookie.cookie) {
381     case 0:
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;
387             goto out;
388         }
389 
390         entry = (nfs41_readdir_entry*)entry_buf;
391         ZeroMemory(&entry->attr_info, sizeof(nfs41_file_info));
392 
393         status = nfs41_cached_getattr(state->session,
394             &state->file, &entry->attr_info);
395         if (status) {
396             dprintf(1, "failed to add '.' entry.\n");
397             goto out;
398         }
399         entry->cookie = COOKIE_DOT;
400         entry->name_len = 2;
401         StringCbCopyA(entry->name, entry->name_len, ".");
402         entry->next_entry_offset = entry_len + entry->name_len;
403 
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;
408         if (args->single)
409             break;
410         /* else no break! */
411     case COOKIE_DOT:
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;
417             goto out;
418         }
419         /* XXX: this skips '..' when listing root fh */
420         if (state->file.name.len == 0)
421             break;
422 
423         entry = (nfs41_readdir_entry*)entry_buf;
424         ZeroMemory(&entry->attr_info, sizeof(nfs41_file_info));
425 
426         status = nfs41_cached_getattr(state->session,
427             &state->parent, &entry->attr_info);
428         if (status) {
429             status = ERROR_FILE_NOT_FOUND;
430             dprintf(1, "failed to add '..' entry.\n");
431             goto out;
432         }
433         entry->cookie = COOKIE_DOTDOT;
434         entry->name_len = 3;
435         StringCbCopyA(entry->name, entry->name_len, "..");
436         entry->next_entry_offset = entry_len + entry->name_len;
437 
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;
442         break;
443     }
444     if (state->cookie.cookie == COOKIE_DOTDOT ||
445         state->cookie.cookie == COOKIE_DOT)
446         ZeroMemory(&state->cookie, sizeof(nfs41_readdir_cookie));
447 out:
448     return status;
449 }
450 
451 static int handle_readdir(nfs41_upcall *upcall)
452 {
453     int status;
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;
459     bool_t eof;
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);
463 
464     dprintf(1, "-> handle_nfs41_dirquery(%s,%d,%d,%d)\n",
465         args->filter, args->initial, args->restart, args->single);
466 
467     args->query_reply_len = 0;
468 
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;
482         goto out;
483     }
484 
485     entry_buf = calloc(max_buf_len, sizeof(unsigned char));
486     if (entry_buf == NULL) {
487         status = GetLastError();
488         goto out_free_cookie;
489     }
490 fetch_entries:
491     entry_buf_len = max_buf_len;
492 
493     nfs41_superblock_getattr_mask(state->file.fh.superblock, &attr_request);
494     attr_request.arr[0] |= FATTR4_WORD0_RDATTR_ERROR;
495 
496     if (strchr(args->filter, FILTER_STAR) || strchr(args->filter, FILTER_QM)) {
497         /* use READDIR for wildcards */
498 
499         uint32_t dots_len = 0;
500         uint32_t *dots_next_offset = NULL;
501 
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);
505             if (status)
506                 goto out_free_cookie;
507             entry_buf_len -= dots_len;
508         }
509 
510         if (dots_len && args->single) {
511             dprintf(2, "skipping nfs41_readdir because the single query "
512                 "will use . or ..\n");
513             entry_buf_len = 0;
514             eof = 0;
515         } else {
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);
521             if (status) {
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;
526             }
527         }
528 
529         if (!entry_buf_len && dots_next_offset)
530             *dots_next_offset = 0;
531         entry_buf_len += dots_len;
532     } else {
533         /* use LOOKUP for single files */
534         nfs41_readdir_entry *entry = (nfs41_readdir_entry*)entry_buf;
535         entry->cookie = 0;
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;
539 
540         status = lookup_entry(upcall->root_ref,
541              state->session, &state->file, entry);
542         if (status) {
543             dprintf(1, "single_lookup failed with %d\n", status);
544             goto out_free_cookie;
545         }
546         entry_buf_len = entry->name_len +
547                 FIELD_OFFSET(nfs41_readdir_entry, name);
548 
549         eof = 1;
550     }
551 
552     status = args->initial ? ERROR_FILE_NOT_FOUND : ERROR_NO_MORE_FILES;
553 
554     if (entry_buf_len) {
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;
560 
561         for (;;) {
562             entry = (nfs41_readdir_entry*)entry_pos;
563             offset = (PULONG)dst_pos; /* ULONG NextEntryOffset */
564 
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)) {
569                     eof = 0;
570                     dprintf(2, "not enough space to copy entry %s (cookie %d)\n",
571                         entry->name, entry->cookie);
572                     break;
573                 }
574                 last_offset = offset;
575                 status = NO_ERROR;
576             }
577             state->cookie.cookie = entry->cookie;
578 
579             /* last entry we got from the server */
580             if (!entry->next_entry_offset)
581                 break;
582 
583             /* we found our single entry, but the server has more */
584             if (args->single && last_offset) {
585                 eof = 0;
586                 break;
587             }
588             entry_pos += entry->next_entry_offset;
589         }
590         args->query_reply_len = args->buf_len - dst_len;
591         if (last_offset) {
592             *last_offset = 0;
593         } else if (!eof) {
594             dprintf(1, "no entries matched; fetch more\n");
595             goto fetch_entries;
596         }
597     }
598 
599     if (eof) {
600         dprintf(1, "we don't need to save a cookie\n");
601         goto out_free_cookie;
602     } else
603         dprintf(1, "saving cookie %llu\n", state->cookie.cookie);
604 
605 out_free_entry:
606     free(entry_buf);
607 out:
608     dprintf(1, "<- handle_nfs41_dirquery(%s,%d,%d,%d) returning ",
609         args->filter, args->initial, args->restart, args->single);
610     if (status) {
611         switch (status) {
612         case ERROR_FILE_NOT_FOUND:
613             dprintf(1, "ERROR_FILE_NOT_FOUND.\n");
614             break;
615         case ERROR_NO_MORE_FILES:
616             dprintf(1, "ERROR_NO_MORE_FILES.\n");
617             break;
618         case ERROR_BUFFER_OVERFLOW:
619             upcall->last_error = status;
620             status = ERROR_SUCCESS;
621             break;
622         default:
623             dprintf(1, "error code %d.\n", status);
624             break;
625         }
626     } else {
627         dprintf(1, "success!\n");
628     }
629     return status;
630 out_free_cookie:
631     state->cookie.cookie = 0;
632     goto out_free_entry;
633 }
634 
635 static int marshall_readdir(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall)
636 {
637     int status;
638     readdir_upcall_args *args = &upcall->args.readdir;
639 
640     status = safe_write(&buffer, length, &args->query_reply_len, sizeof(args->query_reply_len));
641     return status;
642 }
643 
644 
645 const nfs41_upcall_op nfs41_op_readdir = {
646     parse_readdir,
647     handle_readdir,
648     marshall_readdir
649 };
650