1 /* Copyright (c) Mark Harmstone 2016-17 2 * 3 * This file is part of WinBtrfs. 4 * 5 * WinBtrfs is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU Lesser General Public Licence as published by 7 * the Free Software Foundation, either version 3 of the Licence, or 8 * (at your option) any later version. 9 * 10 * WinBtrfs is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU Lesser General Public Licence for more details. 14 * 15 * You should have received a copy of the GNU Lesser General Public Licence 16 * along with WinBtrfs. If not, see <http://www.gnu.org/licenses/>. */ 17 18 #include "btrfs_drv.h" 19 20 enum DirEntryType { 21 DirEntryType_File, 22 DirEntryType_Self, 23 DirEntryType_Parent 24 }; 25 26 typedef struct { 27 KEY key; 28 UNICODE_STRING name; 29 UINT8 type; 30 enum DirEntryType dir_entry_type; 31 dir_child* dc; 32 } dir_entry; 33 34 ULONG get_reparse_tag_fcb(fcb* fcb) { 35 ULONG tag; 36 37 if (fcb->type == BTRFS_TYPE_SYMLINK) 38 return IO_REPARSE_TAG_SYMLINK; 39 else if (fcb->type == BTRFS_TYPE_DIRECTORY) { 40 if (!fcb->reparse_xattr.Buffer || fcb->reparse_xattr.Length < sizeof(ULONG)) 41 return 0; 42 43 RtlCopyMemory(&tag, fcb->reparse_xattr.Buffer, sizeof(ULONG)); 44 } else { 45 NTSTATUS Status; 46 ULONG br; 47 48 Status = read_file(fcb, (UINT8*)&tag, 0, sizeof(ULONG), &br, NULL); 49 if (!NT_SUCCESS(Status)) { 50 ERR("read_file returned %08x\n", Status); 51 return 0; 52 } 53 } 54 55 return tag; 56 } 57 58 ULONG get_reparse_tag(device_extension* Vcb, root* subvol, UINT64 inode, UINT8 type, ULONG atts, BOOL lxss, PIRP Irp) { 59 fcb* fcb; 60 ULONG tag = 0; 61 NTSTATUS Status; 62 63 if (type == BTRFS_TYPE_SYMLINK) 64 return IO_REPARSE_TAG_SYMLINK; 65 else if (lxss) { 66 if (type == BTRFS_TYPE_SOCKET) 67 return IO_REPARSE_TAG_LXSS_SOCKET; 68 else if (type == BTRFS_TYPE_FIFO) 69 return IO_REPARSE_TAG_LXSS_FIFO; 70 else if (type == BTRFS_TYPE_CHARDEV) 71 return IO_REPARSE_TAG_LXSS_CHARDEV; 72 else if (type == BTRFS_TYPE_BLOCKDEV) 73 return IO_REPARSE_TAG_LXSS_BLOCKDEV; 74 } 75 76 if (type != BTRFS_TYPE_FILE && type != BTRFS_TYPE_DIRECTORY) 77 return 0; 78 79 if (!(atts & FILE_ATTRIBUTE_REPARSE_POINT)) 80 return 0; 81 82 Status = open_fcb(Vcb, subvol, inode, type, NULL, NULL, &fcb, PagedPool, Irp); 83 if (!NT_SUCCESS(Status)) { 84 ERR("open_fcb returned %08x\n", Status); 85 return 0; 86 } 87 88 ExAcquireResourceSharedLite(fcb->Header.Resource, TRUE); 89 90 tag = get_reparse_tag_fcb(fcb); 91 92 ExReleaseResourceLite(fcb->Header.Resource); 93 94 free_fcb(Vcb, fcb); 95 96 return tag; 97 } 98 99 static ULONG get_ea_len(device_extension* Vcb, root* subvol, UINT64 inode, PIRP Irp) { 100 UINT8* eadata; 101 UINT16 len; 102 103 if (get_xattr(Vcb, subvol, inode, EA_EA, EA_EA_HASH, &eadata, &len, Irp)) { 104 ULONG offset; 105 NTSTATUS Status; 106 107 Status = IoCheckEaBufferValidity((FILE_FULL_EA_INFORMATION*)eadata, len, &offset); 108 109 if (!NT_SUCCESS(Status)) { 110 WARN("IoCheckEaBufferValidity returned %08x (error at offset %u)\n", Status, offset); 111 ExFreePool(eadata); 112 return 0; 113 } else { 114 FILE_FULL_EA_INFORMATION* eainfo; 115 ULONG ealen; 116 117 ealen = 4; 118 eainfo = (FILE_FULL_EA_INFORMATION*)eadata; 119 do { 120 ealen += 5 + eainfo->EaNameLength + eainfo->EaValueLength; 121 122 if (eainfo->NextEntryOffset == 0) 123 break; 124 125 eainfo = (FILE_FULL_EA_INFORMATION*)(((UINT8*)eainfo) + eainfo->NextEntryOffset); 126 } while (TRUE); 127 128 ExFreePool(eadata); 129 130 return ealen; 131 } 132 } else 133 return 0; 134 } 135 136 static NTSTATUS query_dir_item(fcb* fcb, ccb* ccb, void* buf, LONG* len, PIRP Irp, dir_entry* de, root* r) { 137 PIO_STACK_LOCATION IrpSp; 138 LONG needed; 139 UINT64 inode; 140 INODE_ITEM ii; 141 NTSTATUS Status; 142 ULONG atts = 0, ealen = 0; 143 file_ref* fileref = ccb->fileref; 144 145 IrpSp = IoGetCurrentIrpStackLocation(Irp); 146 147 if (de->key.obj_type == TYPE_ROOT_ITEM) { // subvol 148 LIST_ENTRY* le; 149 150 r = NULL; 151 152 le = fcb->Vcb->roots.Flink; 153 while (le != &fcb->Vcb->roots) { 154 root* subvol = CONTAINING_RECORD(le, root, list_entry); 155 156 if (subvol->id == de->key.obj_id) { 157 r = subvol; 158 break; 159 } 160 161 le = le->Flink; 162 } 163 164 if (r && r->parent != fcb->subvol->id) 165 r = NULL; 166 167 inode = SUBVOL_ROOT_INODE; 168 } else { 169 inode = de->key.obj_id; 170 } 171 172 if (IrpSp->Parameters.QueryDirectory.FileInformationClass != FileNamesInformation) { // FIXME - object ID and reparse point classes too? 173 switch (de->dir_entry_type) { 174 case DirEntryType_File: 175 { 176 if (!r) { 177 LARGE_INTEGER time; 178 179 ii = fcb->Vcb->dummy_fcb->inode_item; 180 atts = fcb->Vcb->dummy_fcb->atts; 181 ealen = fcb->Vcb->dummy_fcb->ealen; 182 183 KeQuerySystemTime(&time); 184 win_time_to_unix(time, &ii.otime); 185 ii.st_atime = ii.st_mtime = ii.st_ctime = ii.otime; 186 } else { 187 BOOL found = FALSE; 188 189 if (de->dc && de->dc->fileref && de->dc->fileref->fcb) { 190 ii = de->dc->fileref->fcb->inode_item; 191 atts = de->dc->fileref->fcb->atts; 192 ealen = de->dc->fileref->fcb->ealen; 193 found = TRUE; 194 } 195 196 if (!found) { 197 KEY searchkey; 198 traverse_ptr tp; 199 200 searchkey.obj_id = inode; 201 searchkey.obj_type = TYPE_INODE_ITEM; 202 searchkey.offset = 0xffffffffffffffff; 203 204 Status = find_item(fcb->Vcb, r, &tp, &searchkey, FALSE, Irp); 205 if (!NT_SUCCESS(Status)) { 206 ERR("error - find_item returned %08x\n", Status); 207 return Status; 208 } 209 210 if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) { 211 ERR("could not find inode item for inode %llx in root %llx\n", inode, r->id); 212 return STATUS_INTERNAL_ERROR; 213 } 214 215 RtlZeroMemory(&ii, sizeof(INODE_ITEM)); 216 217 if (tp.item->size > 0) 218 RtlCopyMemory(&ii, tp.item->data, min(sizeof(INODE_ITEM), tp.item->size)); 219 220 if (IrpSp->Parameters.QueryDirectory.FileInformationClass == FileBothDirectoryInformation || 221 IrpSp->Parameters.QueryDirectory.FileInformationClass == FileDirectoryInformation || 222 IrpSp->Parameters.QueryDirectory.FileInformationClass == FileFullDirectoryInformation || 223 IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdBothDirectoryInformation || 224 IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdFullDirectoryInformation) { 225 226 BOOL dotfile = de->name.Length > sizeof(WCHAR) && de->name.Buffer[0] == '.'; 227 228 atts = get_file_attributes(fcb->Vcb, r, inode, de->type, dotfile, FALSE, Irp); 229 } 230 231 if (IrpSp->Parameters.QueryDirectory.FileInformationClass == FileBothDirectoryInformation || 232 IrpSp->Parameters.QueryDirectory.FileInformationClass == FileFullDirectoryInformation || 233 IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdBothDirectoryInformation || 234 IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdFullDirectoryInformation) { 235 ealen = get_ea_len(fcb->Vcb, r, inode, Irp); 236 } 237 } 238 } 239 240 break; 241 } 242 243 case DirEntryType_Self: 244 ii = fcb->inode_item; 245 r = fcb->subvol; 246 inode = fcb->inode; 247 atts = fcb->atts; 248 ealen = fcb->ealen; 249 break; 250 251 case DirEntryType_Parent: 252 if (fileref && fileref->parent) { 253 ii = fileref->parent->fcb->inode_item; 254 r = fileref->parent->fcb->subvol; 255 inode = fileref->parent->fcb->inode; 256 atts = fileref->parent->fcb->atts; 257 ealen = fileref->parent->fcb->ealen; 258 } else { 259 ERR("no fileref\n"); 260 return STATUS_INTERNAL_ERROR; 261 } 262 break; 263 } 264 265 if (atts == 0) 266 atts = FILE_ATTRIBUTE_NORMAL; 267 } 268 269 switch (IrpSp->Parameters.QueryDirectory.FileInformationClass) { 270 case FileBothDirectoryInformation: 271 { 272 FILE_BOTH_DIR_INFORMATION* fbdi = buf; 273 274 TRACE("FileBothDirectoryInformation\n"); 275 276 needed = sizeof(FILE_BOTH_DIR_INFORMATION) - sizeof(WCHAR) + de->name.Length; 277 278 if (needed > *len) { 279 TRACE("buffer overflow - %u > %u\n", needed, *len); 280 return STATUS_BUFFER_OVERFLOW; 281 } 282 283 fbdi->NextEntryOffset = 0; 284 fbdi->FileIndex = 0; 285 fbdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime); 286 fbdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime); 287 fbdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime); 288 fbdi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime); 289 fbdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size; 290 291 if (de->type == BTRFS_TYPE_SYMLINK) 292 fbdi->AllocationSize.QuadPart = 0; 293 else if (atts & FILE_ATTRIBUTE_SPARSE_FILE) 294 fbdi->AllocationSize.QuadPart = ii.st_blocks; 295 else 296 fbdi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size); 297 298 fbdi->FileAttributes = atts; 299 fbdi->FileNameLength = de->name.Length; 300 fbdi->EaSize = (r && atts & FILE_ATTRIBUTE_REPARSE_POINT) ? get_reparse_tag(fcb->Vcb, r, inode, de->type, atts, ccb->lxss, Irp) : ealen; 301 fbdi->ShortNameLength = 0; 302 303 RtlCopyMemory(fbdi->FileName, de->name.Buffer, de->name.Length); 304 305 *len -= needed; 306 307 return STATUS_SUCCESS; 308 } 309 310 case FileDirectoryInformation: 311 { 312 FILE_DIRECTORY_INFORMATION* fdi = buf; 313 314 TRACE("FileDirectoryInformation\n"); 315 316 needed = sizeof(FILE_DIRECTORY_INFORMATION) - sizeof(WCHAR) + de->name.Length; 317 318 if (needed > *len) { 319 TRACE("buffer overflow - %u > %u\n", needed, *len); 320 return STATUS_BUFFER_OVERFLOW; 321 } 322 323 fdi->NextEntryOffset = 0; 324 fdi->FileIndex = 0; 325 fdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime); 326 fdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime); 327 fdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime); 328 fdi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime); 329 fdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size; 330 331 if (de->type == BTRFS_TYPE_SYMLINK) 332 fdi->AllocationSize.QuadPart = 0; 333 else if (atts & FILE_ATTRIBUTE_SPARSE_FILE) 334 fdi->AllocationSize.QuadPart = ii.st_blocks; 335 else 336 fdi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size); 337 338 fdi->FileAttributes = atts; 339 fdi->FileNameLength = de->name.Length; 340 341 RtlCopyMemory(fdi->FileName, de->name.Buffer, de->name.Length); 342 343 *len -= needed; 344 345 return STATUS_SUCCESS; 346 } 347 348 case FileFullDirectoryInformation: 349 { 350 FILE_FULL_DIR_INFORMATION* ffdi = buf; 351 352 TRACE("FileFullDirectoryInformation\n"); 353 354 needed = sizeof(FILE_FULL_DIR_INFORMATION) - sizeof(WCHAR) + de->name.Length; 355 356 if (needed > *len) { 357 TRACE("buffer overflow - %u > %u\n", needed, *len); 358 return STATUS_BUFFER_OVERFLOW; 359 } 360 361 ffdi->NextEntryOffset = 0; 362 ffdi->FileIndex = 0; 363 ffdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime); 364 ffdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime); 365 ffdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime); 366 ffdi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime); 367 ffdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size; 368 369 if (de->type == BTRFS_TYPE_SYMLINK) 370 ffdi->AllocationSize.QuadPart = 0; 371 else if (atts & FILE_ATTRIBUTE_SPARSE_FILE) 372 ffdi->AllocationSize.QuadPart = ii.st_blocks; 373 else 374 ffdi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size); 375 376 ffdi->FileAttributes = atts; 377 ffdi->FileNameLength = de->name.Length; 378 ffdi->EaSize = (r && atts & FILE_ATTRIBUTE_REPARSE_POINT) ? get_reparse_tag(fcb->Vcb, r, inode, de->type, atts, ccb->lxss, Irp) : ealen; 379 380 RtlCopyMemory(ffdi->FileName, de->name.Buffer, de->name.Length); 381 382 *len -= needed; 383 384 return STATUS_SUCCESS; 385 } 386 387 case FileIdBothDirectoryInformation: 388 { 389 FILE_ID_BOTH_DIR_INFORMATION* fibdi = buf; 390 391 TRACE("FileIdBothDirectoryInformation\n"); 392 393 needed = sizeof(FILE_ID_BOTH_DIR_INFORMATION) - sizeof(WCHAR) + de->name.Length; 394 395 if (needed > *len) { 396 TRACE("buffer overflow - %u > %u\n", needed, *len); 397 return STATUS_BUFFER_OVERFLOW; 398 } 399 400 fibdi->NextEntryOffset = 0; 401 fibdi->FileIndex = 0; 402 fibdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime); 403 fibdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime); 404 fibdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime); 405 fibdi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime); 406 fibdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size; 407 408 if (de->type == BTRFS_TYPE_SYMLINK) 409 fibdi->AllocationSize.QuadPart = 0; 410 else if (atts & FILE_ATTRIBUTE_SPARSE_FILE) 411 fibdi->AllocationSize.QuadPart = ii.st_blocks; 412 else 413 fibdi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size); 414 415 fibdi->FileAttributes = atts; 416 fibdi->FileNameLength = de->name.Length; 417 fibdi->EaSize = (r && atts & FILE_ATTRIBUTE_REPARSE_POINT) ? get_reparse_tag(fcb->Vcb, r, inode, de->type, atts, ccb->lxss, Irp) : ealen; 418 fibdi->ShortNameLength = 0; 419 fibdi->FileId.QuadPart = r ? make_file_id(r, inode) : make_file_id(fcb->Vcb->dummy_fcb->subvol, fcb->Vcb->dummy_fcb->inode); 420 421 RtlCopyMemory(fibdi->FileName, de->name.Buffer, de->name.Length); 422 423 *len -= needed; 424 425 return STATUS_SUCCESS; 426 } 427 428 case FileIdFullDirectoryInformation: 429 { 430 FILE_ID_FULL_DIR_INFORMATION* fifdi = buf; 431 432 TRACE("FileIdFullDirectoryInformation\n"); 433 434 needed = sizeof(FILE_ID_FULL_DIR_INFORMATION) - sizeof(WCHAR) + de->name.Length; 435 436 if (needed > *len) { 437 TRACE("buffer overflow - %u > %u\n", needed, *len); 438 return STATUS_BUFFER_OVERFLOW; 439 } 440 441 fifdi->NextEntryOffset = 0; 442 fifdi->FileIndex = 0; 443 fifdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime); 444 fifdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime); 445 fifdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime); 446 fifdi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime); 447 fifdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size; 448 449 if (de->type == BTRFS_TYPE_SYMLINK) 450 fifdi->AllocationSize.QuadPart = 0; 451 else if (atts & FILE_ATTRIBUTE_SPARSE_FILE) 452 fifdi->AllocationSize.QuadPart = ii.st_blocks; 453 else 454 fifdi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size); 455 456 fifdi->FileAttributes = atts; 457 fifdi->FileNameLength = de->name.Length; 458 fifdi->EaSize = (r && atts & FILE_ATTRIBUTE_REPARSE_POINT) ? get_reparse_tag(fcb->Vcb, r, inode, de->type, atts, ccb->lxss, Irp) : ealen; 459 fifdi->FileId.QuadPart = r ? make_file_id(r, inode) : make_file_id(fcb->Vcb->dummy_fcb->subvol, fcb->Vcb->dummy_fcb->inode); 460 461 RtlCopyMemory(fifdi->FileName, de->name.Buffer, de->name.Length); 462 463 *len -= needed; 464 465 return STATUS_SUCCESS; 466 } 467 468 case FileNamesInformation: 469 { 470 FILE_NAMES_INFORMATION* fni = buf; 471 472 TRACE("FileNamesInformation\n"); 473 474 needed = sizeof(FILE_NAMES_INFORMATION) - sizeof(WCHAR) + de->name.Length; 475 476 if (needed > *len) { 477 TRACE("buffer overflow - %u > %u\n", needed, *len); 478 return STATUS_BUFFER_OVERFLOW; 479 } 480 481 fni->NextEntryOffset = 0; 482 fni->FileIndex = 0; 483 fni->FileNameLength = de->name.Length; 484 485 RtlCopyMemory(fni->FileName, de->name.Buffer, de->name.Length); 486 487 *len -= needed; 488 489 return STATUS_SUCCESS; 490 } 491 492 case FileObjectIdInformation: 493 FIXME("STUB: FileObjectIdInformation\n"); 494 return STATUS_NOT_IMPLEMENTED; 495 496 case FileQuotaInformation: 497 FIXME("STUB: FileQuotaInformation\n"); 498 return STATUS_NOT_IMPLEMENTED; 499 500 case FileReparsePointInformation: 501 FIXME("STUB: FileReparsePointInformation\n"); 502 return STATUS_NOT_IMPLEMENTED; 503 504 default: 505 WARN("Unknown FileInformationClass %u\n", IrpSp->Parameters.QueryDirectory.FileInformationClass); 506 return STATUS_NOT_IMPLEMENTED; 507 } 508 509 return STATUS_NO_MORE_FILES; 510 } 511 512 static NTSTATUS next_dir_entry(file_ref* fileref, UINT64* offset, dir_entry* de, dir_child** pdc) { 513 LIST_ENTRY* le; 514 dir_child* dc; 515 516 if (*pdc) { 517 dir_child* dc2 = *pdc; 518 519 if (dc2->list_entry_index.Flink != &fileref->fcb->dir_children_index) 520 dc = CONTAINING_RECORD(dc2->list_entry_index.Flink, dir_child, list_entry_index); 521 else 522 dc = NULL; 523 524 goto next; 525 } 526 527 if (fileref->parent) { // don't return . and .. if root directory 528 if (*offset == 0) { 529 de->key.obj_id = fileref->fcb->inode; 530 de->key.obj_type = TYPE_INODE_ITEM; 531 de->key.offset = 0; 532 de->dir_entry_type = DirEntryType_Self; 533 de->name.Buffer = L"."; 534 de->name.Length = de->name.MaximumLength = sizeof(WCHAR); 535 de->type = BTRFS_TYPE_DIRECTORY; 536 537 *offset = 1; 538 *pdc = NULL; 539 540 return STATUS_SUCCESS; 541 } else if (*offset == 1) { 542 de->key.obj_id = fileref->parent->fcb->inode; 543 de->key.obj_type = TYPE_INODE_ITEM; 544 de->key.offset = 0; 545 de->dir_entry_type = DirEntryType_Parent; 546 de->name.Buffer = L".."; 547 de->name.Length = de->name.MaximumLength = sizeof(WCHAR) * 2; 548 de->type = BTRFS_TYPE_DIRECTORY; 549 550 *offset = 2; 551 *pdc = NULL; 552 553 return STATUS_SUCCESS; 554 } 555 } 556 557 if (*offset < 2) 558 *offset = 2; 559 560 dc = NULL; 561 le = fileref->fcb->dir_children_index.Flink; 562 563 // skip entries before offset 564 while (le != &fileref->fcb->dir_children_index) { 565 dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_index); 566 567 if (dc2->index >= *offset) { 568 dc = dc2; 569 break; 570 } 571 572 le = le->Flink; 573 } 574 575 next: 576 if (!dc) 577 return STATUS_NO_MORE_FILES; 578 579 de->key = dc->key; 580 de->name = dc->name; 581 de->type = dc->type; 582 de->dir_entry_type = DirEntryType_File; 583 de->dc = dc; 584 585 *offset = dc->index + 1; 586 *pdc = dc; 587 588 return STATUS_SUCCESS; 589 } 590 591 static NTSTATUS query_directory(PIRP Irp) { 592 PIO_STACK_LOCATION IrpSp; 593 NTSTATUS Status, status2; 594 fcb* fcb; 595 ccb* ccb; 596 file_ref* fileref; 597 device_extension* Vcb; 598 void* buf; 599 UINT8 *curitem, *lastitem; 600 LONG length; 601 ULONG count; 602 BOOL has_wildcard = FALSE, specific_file = FALSE, initial; 603 dir_entry de; 604 UINT64 newoffset; 605 ANSI_STRING utf8; 606 dir_child* dc = NULL; 607 608 TRACE("query directory\n"); 609 610 IrpSp = IoGetCurrentIrpStackLocation(Irp); 611 fcb = IrpSp->FileObject->FsContext; 612 ccb = IrpSp->FileObject->FsContext2; 613 fileref = ccb ? ccb->fileref : NULL; 614 615 utf8.Buffer = NULL; 616 617 if (!fileref) 618 return STATUS_INVALID_PARAMETER; 619 620 if (!ccb) { 621 ERR("ccb was NULL\n"); 622 return STATUS_INVALID_PARAMETER; 623 } 624 625 if (!fcb) { 626 ERR("fcb was NULL\n"); 627 return STATUS_INVALID_PARAMETER; 628 } 629 630 if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_LIST_DIRECTORY)) { 631 WARN("insufficient privileges\n"); 632 return STATUS_ACCESS_DENIED; 633 } 634 635 Vcb = fcb->Vcb; 636 637 if (!Vcb) { 638 ERR("Vcb was NULL\n"); 639 return STATUS_INVALID_PARAMETER; 640 } 641 642 if (fileref->fcb == Vcb->dummy_fcb) 643 return STATUS_NO_MORE_FILES; 644 645 ExAcquireResourceSharedLite(&Vcb->tree_lock, TRUE); 646 acquire_fcb_lock_shared(Vcb); 647 648 TRACE("%S\n", file_desc(IrpSp->FileObject)); 649 650 if (IrpSp->Flags == 0) { 651 TRACE("QD flags: (none)\n"); 652 } else { 653 ULONG flags = IrpSp->Flags; 654 655 TRACE("QD flags:\n"); 656 657 if (flags & SL_INDEX_SPECIFIED) { 658 TRACE(" SL_INDEX_SPECIFIED\n"); 659 flags &= ~SL_INDEX_SPECIFIED; 660 } 661 662 if (flags & SL_RESTART_SCAN) { 663 TRACE(" SL_RESTART_SCAN\n"); 664 flags &= ~SL_RESTART_SCAN; 665 } 666 667 if (flags & SL_RETURN_SINGLE_ENTRY) { 668 TRACE(" SL_RETURN_SINGLE_ENTRY\n"); 669 flags &= ~SL_RETURN_SINGLE_ENTRY; 670 } 671 672 if (flags != 0) 673 TRACE(" unknown flags: %u\n", flags); 674 } 675 676 if (IrpSp->Flags & SL_RESTART_SCAN) { 677 ccb->query_dir_offset = 0; 678 679 if (ccb->query_string.Buffer) { 680 RtlFreeUnicodeString(&ccb->query_string); 681 ccb->query_string.Buffer = NULL; 682 } 683 684 ccb->has_wildcard = FALSE; 685 ccb->specific_file = FALSE; 686 } 687 688 initial = !ccb->query_string.Buffer; 689 690 if (IrpSp->Parameters.QueryDirectory.FileName && IrpSp->Parameters.QueryDirectory.FileName->Length > 1) { 691 TRACE("QD filename: %.*S\n", IrpSp->Parameters.QueryDirectory.FileName->Length / sizeof(WCHAR), IrpSp->Parameters.QueryDirectory.FileName->Buffer); 692 693 if (IrpSp->Parameters.QueryDirectory.FileName->Length > sizeof(WCHAR) || IrpSp->Parameters.QueryDirectory.FileName->Buffer[0] != L'*') { 694 specific_file = TRUE; 695 696 if (FsRtlDoesNameContainWildCards(IrpSp->Parameters.QueryDirectory.FileName)) { 697 has_wildcard = TRUE; 698 specific_file = FALSE; 699 } 700 } 701 702 if (ccb->query_string.Buffer) 703 RtlFreeUnicodeString(&ccb->query_string); 704 705 if (has_wildcard) 706 RtlUpcaseUnicodeString(&ccb->query_string, IrpSp->Parameters.QueryDirectory.FileName, TRUE); 707 else { 708 ccb->query_string.Buffer = ExAllocatePoolWithTag(PagedPool, IrpSp->Parameters.QueryDirectory.FileName->Length, ALLOC_TAG); 709 if (!ccb->query_string.Buffer) { 710 ERR("out of memory\n"); 711 Status = STATUS_INSUFFICIENT_RESOURCES; 712 goto end2; 713 } 714 715 ccb->query_string.Length = ccb->query_string.MaximumLength = IrpSp->Parameters.QueryDirectory.FileName->Length; 716 RtlCopyMemory(ccb->query_string.Buffer, IrpSp->Parameters.QueryDirectory.FileName->Buffer, IrpSp->Parameters.QueryDirectory.FileName->Length); 717 } 718 719 ccb->has_wildcard = has_wildcard; 720 ccb->specific_file = specific_file; 721 } else { 722 has_wildcard = ccb->has_wildcard; 723 specific_file = ccb->specific_file; 724 725 if (!(IrpSp->Flags & SL_RESTART_SCAN)) { 726 initial = FALSE; 727 728 if (specific_file) { 729 Status = STATUS_NO_MORE_FILES; 730 goto end2; 731 } 732 } 733 } 734 735 if (ccb->query_string.Buffer) { 736 TRACE("query string = %.*S\n", ccb->query_string.Length / sizeof(WCHAR), ccb->query_string.Buffer); 737 } 738 739 newoffset = ccb->query_dir_offset; 740 741 ExAcquireResourceSharedLite(&fileref->fcb->nonpaged->dir_children_lock, TRUE); 742 743 Status = next_dir_entry(fileref, &newoffset, &de, &dc); 744 745 if (!NT_SUCCESS(Status)) { 746 if (Status == STATUS_NO_MORE_FILES && initial) 747 Status = STATUS_NO_SUCH_FILE; 748 goto end; 749 } 750 751 ccb->query_dir_offset = newoffset; 752 753 buf = map_user_buffer(Irp, NormalPagePriority); 754 755 if (Irp->MdlAddress && !buf) { 756 ERR("MmGetSystemAddressForMdlSafe returned NULL\n"); 757 Status = STATUS_INSUFFICIENT_RESOURCES; 758 goto end; 759 } 760 761 length = IrpSp->Parameters.QueryDirectory.Length; 762 763 if (specific_file) { 764 BOOL found = FALSE; 765 UNICODE_STRING us; 766 LIST_ENTRY* le; 767 UINT32 hash; 768 UINT8 c; 769 770 us.Buffer = NULL; 771 772 if (!ccb->case_sensitive) { 773 Status = RtlUpcaseUnicodeString(&us, &ccb->query_string, TRUE); 774 if (!NT_SUCCESS(Status)) { 775 ERR("RtlUpcaseUnicodeString returned %08x\n", Status); 776 goto end; 777 } 778 779 hash = calc_crc32c(0xffffffff, (UINT8*)us.Buffer, us.Length); 780 } else 781 hash = calc_crc32c(0xffffffff, (UINT8*)ccb->query_string.Buffer, ccb->query_string.Length); 782 783 c = hash >> 24; 784 785 if (ccb->case_sensitive) { 786 if (fileref->fcb->hash_ptrs[c]) { 787 le = fileref->fcb->hash_ptrs[c]; 788 while (le != &fileref->fcb->dir_children_hash) { 789 dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_hash); 790 791 if (dc2->hash == hash) { 792 if (dc2->name.Length == ccb->query_string.Length && RtlCompareMemory(dc2->name.Buffer, ccb->query_string.Buffer, ccb->query_string.Length) == ccb->query_string.Length) { 793 found = TRUE; 794 795 de.key = dc2->key; 796 de.name = dc2->name; 797 de.type = dc2->type; 798 de.dir_entry_type = DirEntryType_File; 799 de.dc = dc2; 800 801 break; 802 } 803 } else if (dc2->hash > hash) 804 break; 805 806 le = le->Flink; 807 } 808 } 809 } else { 810 if (fileref->fcb->hash_ptrs_uc[c]) { 811 le = fileref->fcb->hash_ptrs_uc[c]; 812 while (le != &fileref->fcb->dir_children_hash_uc) { 813 dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_hash_uc); 814 815 if (dc2->hash_uc == hash) { 816 if (dc2->name_uc.Length == us.Length && RtlCompareMemory(dc2->name_uc.Buffer, us.Buffer, us.Length) == us.Length) { 817 found = TRUE; 818 819 de.key = dc2->key; 820 de.name = dc2->name; 821 de.type = dc2->type; 822 de.dir_entry_type = DirEntryType_File; 823 de.dc = dc2; 824 825 break; 826 } 827 } else if (dc2->hash_uc > hash) 828 break; 829 830 le = le->Flink; 831 } 832 } 833 } 834 835 if (us.Buffer) 836 ExFreePool(us.Buffer); 837 838 if (!found) { 839 Status = STATUS_NO_SUCH_FILE; 840 goto end; 841 } 842 } else if (has_wildcard) { 843 while (!FsRtlIsNameInExpression(&ccb->query_string, &de.name, !ccb->case_sensitive, NULL)) { 844 newoffset = ccb->query_dir_offset; 845 Status = next_dir_entry(fileref, &newoffset, &de, &dc); 846 847 if (NT_SUCCESS(Status)) 848 ccb->query_dir_offset = newoffset; 849 else { 850 if (Status == STATUS_NO_MORE_FILES && initial) 851 Status = STATUS_NO_SUCH_FILE; 852 853 goto end; 854 } 855 } 856 } 857 858 TRACE("file(0) = %.*S\n", de.name.Length / sizeof(WCHAR), de.name.Buffer); 859 TRACE("offset = %u\n", ccb->query_dir_offset - 1); 860 861 Status = query_dir_item(fcb, ccb, buf, &length, Irp, &de, fcb->subvol); 862 863 count = 0; 864 if (NT_SUCCESS(Status) && !(IrpSp->Flags & SL_RETURN_SINGLE_ENTRY) && !specific_file) { 865 lastitem = (UINT8*)buf; 866 867 while (length > 0) { 868 switch (IrpSp->Parameters.QueryDirectory.FileInformationClass) { 869 case FileBothDirectoryInformation: 870 case FileDirectoryInformation: 871 case FileIdBothDirectoryInformation: 872 case FileFullDirectoryInformation: 873 case FileIdFullDirectoryInformation: 874 length -= length % 8; 875 break; 876 877 case FileNamesInformation: 878 length -= length % 4; 879 break; 880 881 default: 882 WARN("unhandled file information class %u\n", IrpSp->Parameters.QueryDirectory.FileInformationClass); 883 break; 884 } 885 886 if (length > 0) { 887 newoffset = ccb->query_dir_offset; 888 Status = next_dir_entry(fileref, &newoffset, &de, &dc); 889 if (NT_SUCCESS(Status)) { 890 if (!has_wildcard || FsRtlIsNameInExpression(&ccb->query_string, &de.name, !ccb->case_sensitive, NULL)) { 891 curitem = (UINT8*)buf + IrpSp->Parameters.QueryDirectory.Length - length; 892 count++; 893 894 TRACE("file(%u) %u = %.*S\n", count, curitem - (UINT8*)buf, de.name.Length / sizeof(WCHAR), de.name.Buffer); 895 TRACE("offset = %u\n", ccb->query_dir_offset - 1); 896 897 status2 = query_dir_item(fcb, ccb, curitem, &length, Irp, &de, fcb->subvol); 898 899 if (NT_SUCCESS(status2)) { 900 ULONG* lastoffset = (ULONG*)lastitem; 901 902 *lastoffset = (ULONG)(curitem - lastitem); 903 ccb->query_dir_offset = newoffset; 904 905 lastitem = curitem; 906 } else 907 break; 908 } else 909 ccb->query_dir_offset = newoffset; 910 } else { 911 if (Status == STATUS_NO_MORE_FILES) 912 Status = STATUS_SUCCESS; 913 914 break; 915 } 916 } else 917 break; 918 } 919 } 920 921 Irp->IoStatus.Information = IrpSp->Parameters.QueryDirectory.Length - length; 922 923 end: 924 ExReleaseResourceLite(&fileref->fcb->nonpaged->dir_children_lock); 925 926 end2: 927 release_fcb_lock(Vcb); 928 ExReleaseResourceLite(&Vcb->tree_lock); 929 930 TRACE("returning %08x\n", Status); 931 932 if (utf8.Buffer) 933 ExFreePool(utf8.Buffer); 934 935 return Status; 936 } 937 938 static NTSTATUS notify_change_directory(device_extension* Vcb, PIRP Irp) { 939 PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); 940 PFILE_OBJECT FileObject = IrpSp->FileObject; 941 fcb* fcb = FileObject->FsContext; 942 ccb* ccb = FileObject->FsContext2; 943 file_ref* fileref = ccb ? ccb->fileref : NULL; 944 NTSTATUS Status; 945 946 TRACE("IRP_MN_NOTIFY_CHANGE_DIRECTORY\n"); 947 948 if (!ccb) { 949 ERR("ccb was NULL\n"); 950 return STATUS_INVALID_PARAMETER; 951 } 952 953 if (!fileref) { 954 ERR("no fileref\n"); 955 return STATUS_INVALID_PARAMETER; 956 } 957 958 if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_LIST_DIRECTORY)) { 959 WARN("insufficient privileges\n"); 960 return STATUS_ACCESS_DENIED; 961 } 962 963 ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, TRUE); 964 ExAcquireResourceExclusiveLite(fcb->Header.Resource, TRUE); 965 966 if (fcb->type != BTRFS_TYPE_DIRECTORY) { 967 Status = STATUS_INVALID_PARAMETER; 968 goto end; 969 } 970 971 // FIXME - raise exception if FCB marked for deletion? 972 973 TRACE("%S\n", file_desc(FileObject)); 974 975 if (ccb->filename.Length == 0) { 976 ULONG reqlen; 977 978 ccb->filename.MaximumLength = ccb->filename.Length = 0; 979 980 Status = fileref_get_filename(fileref, &ccb->filename, NULL, &reqlen); 981 if (Status == STATUS_BUFFER_OVERFLOW) { 982 ccb->filename.Buffer = ExAllocatePoolWithTag(PagedPool, reqlen, ALLOC_TAG); 983 if (!ccb->filename.Buffer) { 984 ERR("out of memory\n"); 985 Status = STATUS_INSUFFICIENT_RESOURCES; 986 goto end; 987 } 988 989 ccb->filename.MaximumLength = (UINT16)reqlen; 990 991 Status = fileref_get_filename(fileref, &ccb->filename, NULL, &reqlen); 992 if (!NT_SUCCESS(Status)) { 993 ERR("fileref_get_filename returned %08x\n", Status); 994 goto end; 995 } 996 } else { 997 ERR("fileref_get_filename returned %08x\n", Status); 998 goto end; 999 } 1000 } 1001 1002 FsRtlNotifyFilterChangeDirectory(Vcb->NotifySync, &Vcb->DirNotifyList, FileObject->FsContext2, (PSTRING)&ccb->filename, 1003 IrpSp->Flags & SL_WATCH_TREE, FALSE, IrpSp->Parameters.NotifyDirectory.CompletionFilter, Irp, 1004 NULL, NULL, NULL); 1005 1006 Status = STATUS_PENDING; 1007 1008 end: 1009 ExReleaseResourceLite(fcb->Header.Resource); 1010 ExReleaseResourceLite(&fcb->Vcb->tree_lock); 1011 1012 return Status; 1013 } 1014 1015 _Dispatch_type_(IRP_MJ_DIRECTORY_CONTROL) 1016 _Function_class_(DRIVER_DISPATCH) 1017 NTSTATUS NTAPI drv_directory_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { 1018 PIO_STACK_LOCATION IrpSp; 1019 NTSTATUS Status; 1020 ULONG func; 1021 BOOL top_level; 1022 device_extension* Vcb = DeviceObject->DeviceExtension; 1023 1024 FsRtlEnterFileSystem(); 1025 1026 TRACE("directory control\n"); 1027 1028 top_level = is_top_level(Irp); 1029 1030 if (Vcb && Vcb->type == VCB_TYPE_VOLUME) { 1031 Status = vol_directory_control(DeviceObject, Irp); 1032 goto end; 1033 } else if (!Vcb || Vcb->type != VCB_TYPE_FS) { 1034 Status = STATUS_INVALID_PARAMETER; 1035 goto end; 1036 } 1037 1038 IrpSp = IoGetCurrentIrpStackLocation(Irp); 1039 1040 Irp->IoStatus.Information = 0; 1041 1042 func = IrpSp->MinorFunction; 1043 1044 switch (func) { 1045 case IRP_MN_NOTIFY_CHANGE_DIRECTORY: 1046 Status = notify_change_directory(Vcb, Irp); 1047 break; 1048 1049 case IRP_MN_QUERY_DIRECTORY: 1050 Status = query_directory(Irp); 1051 break; 1052 1053 default: 1054 WARN("unknown minor %u\n", func); 1055 Status = STATUS_NOT_IMPLEMENTED; 1056 Irp->IoStatus.Status = Status; 1057 break; 1058 } 1059 1060 if (Status == STATUS_PENDING) 1061 goto exit; 1062 1063 end: 1064 Irp->IoStatus.Status = Status; 1065 1066 IoCompleteRequest(Irp, IO_DISK_INCREMENT); 1067 1068 exit: 1069 TRACE("returning %08x\n", Status); 1070 1071 if (top_level) 1072 IoSetTopLevelIrp(NULL); 1073 1074 FsRtlExitFileSystem(); 1075 1076 return Status; 1077 } 1078