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