[SETUPLIB] Improve the bootloader 'validity' checks -- Addendum to f06734e5 (r74512).
[reactos.git] / base / services / nfsd / readdir.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 <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 };