xref: /reactos/drivers/filesystems/btrfs/dirctrl.c (revision 50cf16b3)
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