xref: /reactos/base/services/nfsd/readdir.c (revision 98e8827a)
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 #ifndef __REACTOS__
169         *name_size_out++;
170 #else
171         (*name_size_out)++;
172 #endif
173         *name_size_out *= sizeof(WCHAR);
174     }
175 }
176 
177 static void readdir_copy_full_dir_info(
178     IN nfs41_readdir_entry *entry,
179     IN PFILE_DIR_INFO_UNION info)
180 {
181     readdir_copy_dir_info(entry, info);
182     /* for files with the FILE_ATTRIBUTE_REPARSE_POINT attribute,
183      * EaSize is used instead to specify its reparse tag. this makes
184      * the 'dir' command to show files as <SYMLINK>, and triggers a
185      * FSCTL_GET_REPARSE_POINT to query the symlink target
186      */
187     info->fifdi.EaSize = entry->attr_info.type == NF4LNK ?
188         IO_REPARSE_TAG_SYMLINK : 0;
189 }
190 
191 static void readdir_copy_both_dir_info(
192     IN nfs41_readdir_entry *entry,
193     IN LPWSTR wname,
194     IN PFILE_DIR_INFO_UNION info)
195 {
196     readdir_copy_full_dir_info(entry, info);
197     readdir_copy_shortname(wname, info->fbdi.ShortName,
198         &info->fbdi.ShortNameLength);
199 }
200 
201 static void readdir_copy_filename(
202     IN LPCWSTR name,
203     IN uint32_t name_size,
204     OUT LPWSTR name_out,
205     OUT ULONG *name_size_out)
206 {
207     *name_size_out = name_size;
208     memcpy(name_out, name, name_size);
209 }
210 
211 static int format_abs_path(
212     IN const nfs41_abs_path *path,
213     IN const nfs41_component *name,
214     OUT nfs41_abs_path *path_out)
215 {
216     /* format an absolute path 'parent\name' */
217     int status = NO_ERROR;
218 
219     InitializeSRWLock(&path_out->lock);
220     abs_path_copy(path_out, path);
221     if (FAILED(StringCchPrintfA(path_out->path + path_out->len,
222         NFS41_MAX_PATH_LEN - path_out->len, "\\%s", name->name))) {
223         status = ERROR_FILENAME_EXCED_RANGE;
224         goto out;
225     }
226     path_out->len += name->len + 1;
227 out:
228     return status;
229 }
230 
231 static int lookup_entry(
232     IN nfs41_root *root,
233     IN nfs41_session *session,
234     IN nfs41_path_fh *parent,
235     OUT nfs41_readdir_entry *entry)
236 {
237     nfs41_abs_path path;
238     nfs41_component name;
239     int status;
240 
241     name.name = entry->name;
242     name.len = (unsigned short)entry->name_len - 1;
243 
244     status = format_abs_path(parent->path, &name, &path);
245     if (status) goto out;
246 
247     status = nfs41_lookup(root, session, &path,
248         NULL, NULL, &entry->attr_info, NULL);
249     if (status) goto out;
250 out:
251     return status;
252 }
253 
254 static int lookup_symlink(
255     IN nfs41_root *root,
256     IN nfs41_session *session,
257     IN nfs41_path_fh *parent,
258     IN const nfs41_component *name,
259     OUT nfs41_file_info *info_out)
260 {
261     nfs41_abs_path path;
262     nfs41_path_fh file;
263     nfs41_file_info info;
264     int status;
265 
266     status = format_abs_path(parent->path, name, &path);
267     if (status) goto out;
268 
269     file.path = &path;
270     status = nfs41_lookup(root, session, &path, NULL, &file, &info, &session);
271     if (status) goto out;
272 
273     last_component(path.path, path.path + path.len, &file.name);
274 
275     status = nfs41_symlink_follow(root, session, &file, &info);
276     if (status) goto out;
277 
278     info_out->symlink_dir = info.type == NF4DIR;
279 out:
280     return status;
281 }
282 
283 static int readdir_copy_entry(
284     IN readdir_upcall_args *args,
285     IN nfs41_readdir_entry *entry,
286     IN OUT unsigned char **dst_pos,
287     IN OUT uint32_t *dst_len)
288 {
289     int status = 0;
290     WCHAR wname[NFS4_OPAQUE_LIMIT];
291     uint32_t wname_len, wname_size, needed;
292     PFILE_DIR_INFO_UNION info;
293 
294     wname_len = MultiByteToWideChar(CP_UTF8, 0,
295         entry->name, entry->name_len, wname, NFS4_OPAQUE_LIMIT);
296     wname_size = (wname_len - 1) * sizeof(WCHAR);
297 
298     needed = readdir_size_for_entry(args->query_class, wname_size);
299     if (!needed || needed > *dst_len) {
300         status = -1;
301         goto out;
302     }
303 
304     info = (PFILE_DIR_INFO_UNION)*dst_pos;
305     info->NextEntryOffset = align8(needed);
306     *dst_pos += info->NextEntryOffset;
307     *dst_len -= info->NextEntryOffset;
308 
309     if (entry->attr_info.rdattr_error == NFS4ERR_MOVED) {
310         entry->attr_info.type = NF4DIR; /* default to dir */
311         /* look up attributes for referral entries, but ignore return value;
312          * it's okay if lookup fails, we'll just write garbage attributes */
313         lookup_entry(args->root, args->state->session,
314             &args->state->file, entry);
315     } else if (entry->attr_info.type == NF4LNK) {
316         nfs41_component name;
317         name.name = entry->name;
318         name.len = (unsigned short)entry->name_len - 1;
319         /* look up the symlink target to see whether it's a directory */
320         lookup_symlink(args->root, args->state->session,
321             &args->state->file, &name, &entry->attr_info);
322     }
323 
324     switch (args->query_class)
325     {
326     case FileNamesInformation:
327         info->fni.FileIndex = 0;
328         readdir_copy_filename(wname, wname_size,
329             info->fni.FileName, &info->fni.FileNameLength);
330         break;
331     case FileDirectoryInformation:
332         readdir_copy_dir_info(entry, info);
333         readdir_copy_filename(wname, wname_size,
334             info->fdi.FileName, &info->fdi.FileNameLength);
335         break;
336     case FileFullDirectoryInformation:
337         readdir_copy_full_dir_info(entry, info);
338         readdir_copy_filename(wname, wname_size,
339             info->ffdi.FileName, &info->ffdi.FileNameLength);
340         break;
341     case FileIdFullDirectoryInformation:
342         readdir_copy_full_dir_info(entry, info);
343         info->fibdi.FileId.QuadPart = (LONGLONG)entry->attr_info.fileid;
344         readdir_copy_filename(wname, wname_size,
345             info->fifdi.FileName, &info->fifdi.FileNameLength);
346         break;
347     case FileBothDirectoryInformation:
348         readdir_copy_both_dir_info(entry, wname, info);
349         readdir_copy_filename(wname, wname_size,
350             info->fbdi.FileName, &info->fbdi.FileNameLength);
351         break;
352     case FileIdBothDirectoryInformation:
353         readdir_copy_both_dir_info(entry, wname, info);
354         info->fibdi.FileId.QuadPart = (LONGLONG)entry->attr_info.fileid;
355         readdir_copy_filename(wname, wname_size,
356             info->fibdi.FileName, &info->fibdi.FileNameLength);
357         break;
358     default:
359         eprintf("unhandled dir query class %d\n", args->query_class);
360         status = -1;
361         break;
362     }
363 out:
364     return status;
365 }
366 
367 #define COOKIE_DOT      ((uint64_t)-2)
368 #define COOKIE_DOTDOT   ((uint64_t)-1)
369 
370 static int readdir_add_dots(
371     IN readdir_upcall_args *args,
372     IN OUT unsigned char *entry_buf,
373     IN uint32_t entry_buf_len,
374     OUT uint32_t *len_out,
375     OUT uint32_t **last_offset)
376 {
377     int status = 0;
378     const uint32_t entry_len = (uint32_t)FIELD_OFFSET(nfs41_readdir_entry, name);
379     nfs41_readdir_entry *entry;
380     nfs41_open_state *state = args->state;
381 
382     *len_out = 0;
383     *last_offset = NULL;
384     switch (state->cookie.cookie) {
385     case 0:
386         if (entry_buf_len < entry_len + 2) {
387             status = ERROR_BUFFER_OVERFLOW;
388             dprintf(1, "not enough room for '.' entry. received %d need %d\n",
389                     entry_buf_len, entry_len + 2);
390             args->query_reply_len = entry_len + 2;
391             goto out;
392         }
393 
394         entry = (nfs41_readdir_entry*)entry_buf;
395         ZeroMemory(&entry->attr_info, sizeof(nfs41_file_info));
396 
397         status = nfs41_cached_getattr(state->session,
398             &state->file, &entry->attr_info);
399         if (status) {
400             dprintf(1, "failed to add '.' entry.\n");
401             goto out;
402         }
403         entry->cookie = COOKIE_DOT;
404         entry->name_len = 2;
405         StringCbCopyA(entry->name, entry->name_len, ".");
406         entry->next_entry_offset = entry_len + entry->name_len;
407 
408         entry_buf += entry->next_entry_offset;
409         entry_buf_len -= entry->next_entry_offset;
410         *len_out += entry->next_entry_offset;
411         *last_offset = &entry->next_entry_offset;
412         if (args->single)
413             break;
414         /* else no break! */
415     case COOKIE_DOT:
416         if (entry_buf_len < entry_len + 3) {
417             status = ERROR_BUFFER_OVERFLOW;
418             dprintf(1, "not enough room for '..' entry. received %d need %d\n",
419                     entry_buf_len, entry_len);
420             args->query_reply_len = entry_len + 2;
421             goto out;
422         }
423         /* XXX: this skips '..' when listing root fh */
424         if (state->file.name.len == 0)
425             break;
426 
427         entry = (nfs41_readdir_entry*)entry_buf;
428         ZeroMemory(&entry->attr_info, sizeof(nfs41_file_info));
429 
430         status = nfs41_cached_getattr(state->session,
431             &state->parent, &entry->attr_info);
432         if (status) {
433             status = ERROR_FILE_NOT_FOUND;
434             dprintf(1, "failed to add '..' entry.\n");
435             goto out;
436         }
437         entry->cookie = COOKIE_DOTDOT;
438         entry->name_len = 3;
439         StringCbCopyA(entry->name, entry->name_len, "..");
440         entry->next_entry_offset = entry_len + entry->name_len;
441 
442         entry_buf += entry->next_entry_offset;
443         entry_buf_len -= entry->next_entry_offset;
444         *len_out += entry->next_entry_offset;
445         *last_offset = &entry->next_entry_offset;
446         break;
447     }
448     if (state->cookie.cookie == COOKIE_DOTDOT ||
449         state->cookie.cookie == COOKIE_DOT)
450         ZeroMemory(&state->cookie, sizeof(nfs41_readdir_cookie));
451 out:
452     return status;
453 }
454 
455 static int handle_readdir(nfs41_upcall *upcall)
456 {
457     int status;
458     readdir_upcall_args *args = &upcall->args.readdir;
459     nfs41_open_state *state = upcall->state_ref;
460     unsigned char *entry_buf = NULL;
461     uint32_t entry_buf_len;
462     bitmap4 attr_request;
463     bool_t eof;
464     /* make sure we allocate enough space for one nfs41_readdir_entry */
465     const uint32_t max_buf_len = max(args->buf_len,
466         sizeof(nfs41_readdir_entry) + NFS41_MAX_COMPONENT_LEN);
467 
468     dprintf(1, "-> handle_nfs41_dirquery(%s,%d,%d,%d)\n",
469         args->filter, args->initial, args->restart, args->single);
470 
471     args->query_reply_len = 0;
472 
473     if (args->initial || args->restart) {
474         ZeroMemory(&state->cookie, sizeof(nfs41_readdir_cookie));
475         if (!state->cookie.cookie)
476             dprintf(1, "initializing the 1st readdir cookie\n");
477         else if (args->restart)
478             dprintf(1, "restarting; clearing previous cookie %llu\n",
479                 state->cookie.cookie);
480         else if (args->initial)
481             dprintf(1, "*** initial; clearing previous cookie %llu!\n",
482                 state->cookie.cookie);
483     } else if (!state->cookie.cookie) {
484         dprintf(1, "handle_nfs41_readdir: EOF\n");
485         status = ERROR_NO_MORE_FILES;
486         goto out;
487     }
488 
489     entry_buf = calloc(max_buf_len, sizeof(unsigned char));
490     if (entry_buf == NULL) {
491         status = GetLastError();
492         goto out_free_cookie;
493     }
494 fetch_entries:
495     entry_buf_len = max_buf_len;
496 
497     nfs41_superblock_getattr_mask(state->file.fh.superblock, &attr_request);
498     attr_request.arr[0] |= FATTR4_WORD0_RDATTR_ERROR;
499 
500     if (strchr(args->filter, FILTER_STAR) || strchr(args->filter, FILTER_QM)) {
501         /* use READDIR for wildcards */
502 
503         uint32_t dots_len = 0;
504         uint32_t *dots_next_offset = NULL;
505 
506         if (args->filter[0] == '*' && args->filter[1] == '\0') {
507             status = readdir_add_dots(args, entry_buf,
508                 entry_buf_len, &dots_len, &dots_next_offset);
509             if (status)
510                 goto out_free_cookie;
511             entry_buf_len -= dots_len;
512         }
513 
514         if (dots_len && args->single) {
515             dprintf(2, "skipping nfs41_readdir because the single query "
516                 "will use . or ..\n");
517             entry_buf_len = 0;
518             eof = 0;
519         } else {
520             dprintf(2, "calling nfs41_readdir with cookie %llu\n",
521                 state->cookie.cookie);
522             status = nfs41_readdir(state->session, &state->file,
523                 &attr_request, &state->cookie, entry_buf + dots_len,
524                 &entry_buf_len, &eof);
525             if (status) {
526                 dprintf(1, "nfs41_readdir failed with %s\n",
527                     nfs_error_string(status));
528                 status = nfs_to_windows_error(status, ERROR_BAD_NET_RESP);
529                 goto out_free_cookie;
530             }
531         }
532 
533         if (!entry_buf_len && dots_next_offset)
534             *dots_next_offset = 0;
535         entry_buf_len += dots_len;
536     } else {
537         /* use LOOKUP for single files */
538         nfs41_readdir_entry *entry = (nfs41_readdir_entry*)entry_buf;
539         entry->cookie = 0;
540         entry->name_len = (uint32_t)strlen(args->filter) + 1;
541         StringCbCopyA(entry->name, entry->name_len, args->filter);
542         entry->next_entry_offset = 0;
543 
544         status = lookup_entry(upcall->root_ref,
545              state->session, &state->file, entry);
546         if (status) {
547             dprintf(1, "single_lookup failed with %d\n", status);
548             goto out_free_cookie;
549         }
550         entry_buf_len = entry->name_len +
551                 FIELD_OFFSET(nfs41_readdir_entry, name);
552 
553         eof = 1;
554     }
555 
556     status = args->initial ? ERROR_FILE_NOT_FOUND : ERROR_NO_MORE_FILES;
557 
558     if (entry_buf_len) {
559         unsigned char *entry_pos = entry_buf;
560         unsigned char *dst_pos = args->kbuf;
561         uint32_t dst_len = args->buf_len;
562         nfs41_readdir_entry *entry;
563         PULONG offset, last_offset = NULL;
564 
565         for (;;) {
566             entry = (nfs41_readdir_entry*)entry_pos;
567             offset = (PULONG)dst_pos; /* ULONG NextEntryOffset */
568 
569             dprintf(2, "filter %s looking at %s with cookie %d\n",
570                 args->filter, entry->name, entry->cookie);
571             if (readdir_filter((const char*)args->filter, entry->name)) {
572                 if (readdir_copy_entry(args, entry, &dst_pos, &dst_len)) {
573                     eof = 0;
574                     dprintf(2, "not enough space to copy entry %s (cookie %d)\n",
575                         entry->name, entry->cookie);
576                     break;
577                 }
578                 last_offset = offset;
579                 status = NO_ERROR;
580             }
581             state->cookie.cookie = entry->cookie;
582 
583             /* last entry we got from the server */
584             if (!entry->next_entry_offset)
585                 break;
586 
587             /* we found our single entry, but the server has more */
588             if (args->single && last_offset) {
589                 eof = 0;
590                 break;
591             }
592             entry_pos += entry->next_entry_offset;
593         }
594         args->query_reply_len = args->buf_len - dst_len;
595         if (last_offset) {
596             *last_offset = 0;
597         } else if (!eof) {
598             dprintf(1, "no entries matched; fetch more\n");
599             goto fetch_entries;
600         }
601     }
602 
603     if (eof) {
604         dprintf(1, "we don't need to save a cookie\n");
605         goto out_free_cookie;
606     } else
607         dprintf(1, "saving cookie %llu\n", state->cookie.cookie);
608 
609 out_free_entry:
610     free(entry_buf);
611 out:
612     dprintf(1, "<- handle_nfs41_dirquery(%s,%d,%d,%d) returning ",
613         args->filter, args->initial, args->restart, args->single);
614     if (status) {
615         switch (status) {
616         case ERROR_FILE_NOT_FOUND:
617             dprintf(1, "ERROR_FILE_NOT_FOUND.\n");
618             break;
619         case ERROR_NO_MORE_FILES:
620             dprintf(1, "ERROR_NO_MORE_FILES.\n");
621             break;
622         case ERROR_BUFFER_OVERFLOW:
623             upcall->last_error = status;
624             status = ERROR_SUCCESS;
625             break;
626         default:
627             dprintf(1, "error code %d.\n", status);
628             break;
629         }
630     } else {
631         dprintf(1, "success!\n");
632     }
633     return status;
634 out_free_cookie:
635     state->cookie.cookie = 0;
636     goto out_free_entry;
637 }
638 
639 static int marshall_readdir(unsigned char *buffer, uint32_t *length, nfs41_upcall *upcall)
640 {
641     int status;
642     readdir_upcall_args *args = &upcall->args.readdir;
643 
644     status = safe_write(&buffer, length, &args->query_reply_len, sizeof(args->query_reply_len));
645     return status;
646 }
647 
648 
649 const nfs41_upcall_op nfs41_op_readdir = {
650     parse_readdir,
651     handle_readdir,
652     marshall_readdir
653 };
654