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