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