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