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