xref: /reactos/drivers/filesystems/btrfs/reparse.c (revision bd712186)
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 extern tFsRtlValidateReparsePointBuffer fFsRtlValidateReparsePointBuffer;
21 
22 NTSTATUS get_reparse_point(PDEVICE_OBJECT DeviceObject, PFILE_OBJECT FileObject, void* buffer, DWORD buflen, ULONG_PTR* retlen) {
23     USHORT subnamelen, printnamelen, i;
24     ULONG stringlen;
25     DWORD reqlen;
26     REPARSE_DATA_BUFFER* rdb = buffer;
27     fcb* fcb = FileObject->FsContext;
28     ccb* ccb = FileObject->FsContext2;
29     NTSTATUS Status;
30 
31     TRACE("(%p, %p, %p, %x, %p)\n", DeviceObject, FileObject, buffer, buflen, retlen);
32 
33     if (!ccb)
34         return STATUS_INVALID_PARAMETER;
35 
36     ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true);
37     ExAcquireResourceSharedLite(fcb->Header.Resource, true);
38 
39     if (fcb->type == BTRFS_TYPE_SYMLINK) {
40         if (ccb->lxss) {
41             reqlen = offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + sizeof(uint32_t);
42 
43             if (buflen < reqlen) {
44                 Status = STATUS_BUFFER_OVERFLOW;
45                 goto end;
46             }
47 
48             rdb->ReparseTag = IO_REPARSE_TAG_LXSS_SYMLINK;
49             rdb->ReparseDataLength = offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + sizeof(uint32_t);
50             rdb->Reserved = 0;
51 
52             *((uint32_t*)rdb->GenericReparseBuffer.DataBuffer) = 1;
53 
54             *retlen = reqlen;
55         } else {
56             char* data;
57 
58             if (fcb->inode_item.st_size == 0 || fcb->inode_item.st_size > 0xffff) {
59                 Status = STATUS_INVALID_PARAMETER;
60                 goto end;
61             }
62 
63             data = ExAllocatePoolWithTag(PagedPool, (ULONG)fcb->inode_item.st_size, ALLOC_TAG);
64             if (!data) {
65                 ERR("out of memory\n");
66                 Status = STATUS_INSUFFICIENT_RESOURCES;
67                 goto end;
68             }
69 
70             TRACE("data = %p, size = %x\n", data, fcb->inode_item.st_size);
71             Status = read_file(fcb, (uint8_t*)data, 0, fcb->inode_item.st_size, NULL, NULL);
72 
73             if (!NT_SUCCESS(Status)) {
74                 ERR("read_file returned %08x\n", Status);
75                 ExFreePool(data);
76                 goto end;
77             }
78 
79             Status = utf8_to_utf16(NULL, 0, &stringlen, data, (ULONG)fcb->inode_item.st_size);
80             if (!NT_SUCCESS(Status)) {
81                 ERR("utf8_to_utf16 1 returned %08x\n", Status);
82                 ExFreePool(data);
83                 goto end;
84             }
85 
86             subnamelen = (uint16_t)stringlen;
87             printnamelen = (uint16_t)stringlen;
88 
89             reqlen = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) + subnamelen + printnamelen;
90 
91             if (buflen >= offsetof(REPARSE_DATA_BUFFER, ReparseDataLength))
92                 rdb->ReparseTag = IO_REPARSE_TAG_SYMLINK;
93 
94             if (buflen >= offsetof(REPARSE_DATA_BUFFER, Reserved))
95                 rdb->ReparseDataLength = (USHORT)(reqlen - offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer));
96 
97             if (buflen >= offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.SubstituteNameOffset))
98                 rdb->Reserved = 0;
99 
100             if (buflen < reqlen) {
101                 ExFreePool(data);
102                 Status = STATUS_BUFFER_OVERFLOW;
103                 *retlen = min(buflen, offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.SubstituteNameOffset));
104                 goto end;
105             }
106 
107             rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0;
108             rdb->SymbolicLinkReparseBuffer.SubstituteNameLength = subnamelen;
109             rdb->SymbolicLinkReparseBuffer.PrintNameOffset = subnamelen;
110             rdb->SymbolicLinkReparseBuffer.PrintNameLength = printnamelen;
111             rdb->SymbolicLinkReparseBuffer.Flags = SYMLINK_FLAG_RELATIVE;
112 
113             Status = utf8_to_utf16(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)],
114                                        stringlen, &stringlen, data, (ULONG)fcb->inode_item.st_size);
115 
116             if (!NT_SUCCESS(Status)) {
117                 ERR("utf8_to_utf16 2 returned %08x\n", Status);
118                 ExFreePool(data);
119                 goto end;
120             }
121 
122             for (i = 0; i < stringlen / sizeof(WCHAR); i++) {
123                 if (rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] == '/')
124                     rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] = '\\';
125             }
126 
127             RtlCopyMemory(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)],
128                         &rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)],
129                         rdb->SymbolicLinkReparseBuffer.SubstituteNameLength);
130 
131             *retlen = reqlen;
132 
133             ExFreePool(data);
134         }
135 
136         Status = STATUS_SUCCESS;
137     } else if (fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT) {
138         if (fcb->type == BTRFS_TYPE_FILE) {
139             ULONG len;
140 
141             Status = read_file(fcb, buffer, 0, buflen, &len, NULL);
142 
143             if (!NT_SUCCESS(Status)) {
144                 ERR("read_file returned %08x\n", Status);
145             }
146 
147             *retlen = len;
148         } else if (fcb->type == BTRFS_TYPE_DIRECTORY) {
149             if (!fcb->reparse_xattr.Buffer || fcb->reparse_xattr.Length < sizeof(ULONG)) {
150                 Status = STATUS_NOT_A_REPARSE_POINT;
151                 goto end;
152             }
153 
154             if (buflen > 0) {
155                 *retlen = min(buflen, fcb->reparse_xattr.Length);
156                 RtlCopyMemory(buffer, fcb->reparse_xattr.Buffer, *retlen);
157             } else
158                 *retlen = 0;
159 
160             Status = *retlen == fcb->reparse_xattr.Length ? STATUS_SUCCESS : STATUS_BUFFER_OVERFLOW;
161         } else
162             Status = STATUS_NOT_A_REPARSE_POINT;
163     } else {
164         Status = STATUS_NOT_A_REPARSE_POINT;
165     }
166 
167 end:
168     ExReleaseResourceLite(fcb->Header.Resource);
169     ExReleaseResourceLite(&fcb->Vcb->tree_lock);
170 
171     return Status;
172 }
173 
174 static NTSTATUS set_symlink(PIRP Irp, file_ref* fileref, fcb* fcb, ccb* ccb, REPARSE_DATA_BUFFER* rdb, ULONG buflen, bool write, LIST_ENTRY* rollback) {
175     NTSTATUS Status;
176     ULONG minlen;
177     ULONG tlength;
178     UNICODE_STRING subname;
179     ANSI_STRING target;
180     LARGE_INTEGER offset, time;
181     BTRFS_TIME now;
182     USHORT i;
183 
184     if (write) {
185         minlen = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) + sizeof(WCHAR);
186         if (buflen < minlen) {
187             WARN("buffer was less than minimum length (%u < %u)\n", buflen, minlen);
188             return STATUS_INVALID_PARAMETER;
189         }
190 
191         if (rdb->SymbolicLinkReparseBuffer.SubstituteNameLength < sizeof(WCHAR)) {
192             WARN("rdb->SymbolicLinkReparseBuffer.SubstituteNameLength was too short\n");
193             return STATUS_INVALID_PARAMETER;
194         }
195 
196         subname.Buffer = &rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)];
197         subname.MaximumLength = subname.Length = rdb->SymbolicLinkReparseBuffer.SubstituteNameLength;
198 
199         TRACE("substitute name = %.*S\n", subname.Length / sizeof(WCHAR), subname.Buffer);
200     }
201 
202     fcb->type = BTRFS_TYPE_SYMLINK;
203     fcb->inode_item.st_mode |= __S_IFLNK;
204     fcb->inode_item.generation = fcb->Vcb->superblock.generation; // so we don't confuse btrfs send on Linux
205 
206     if (fileref && fileref->dc)
207         fileref->dc->type = fcb->type;
208 
209     if (write) {
210         Status = truncate_file(fcb, 0, Irp, rollback);
211         if (!NT_SUCCESS(Status)) {
212             ERR("truncate_file returned %08x\n", Status);
213             return Status;
214         }
215 
216         Status = utf16_to_utf8(NULL, 0, (PULONG)&target.Length, subname.Buffer, subname.Length);
217         if (!NT_SUCCESS(Status)) {
218             ERR("utf16_to_utf8 1 failed with error %08x\n", Status);
219             return Status;
220         }
221 
222         target.MaximumLength = target.Length;
223         target.Buffer = ExAllocatePoolWithTag(PagedPool, target.MaximumLength, ALLOC_TAG);
224         if (!target.Buffer) {
225             ERR("out of memory\n");
226             return STATUS_INSUFFICIENT_RESOURCES;
227         }
228 
229         Status = utf16_to_utf8(target.Buffer, target.Length, (PULONG)&target.Length, subname.Buffer, subname.Length);
230         if (!NT_SUCCESS(Status)) {
231             ERR("utf16_to_utf8 2 failed with error %08x\n", Status);
232             ExFreePool(target.Buffer);
233             return Status;
234         }
235 
236         for (i = 0; i < target.MaximumLength; i++) {
237             if (target.Buffer[i] == '\\')
238                 target.Buffer[i] = '/';
239         }
240 
241         offset.QuadPart = 0;
242         tlength = target.Length;
243         Status = write_file2(fcb->Vcb, Irp, offset, target.Buffer, &tlength, false, true,
244                              true, false, false, rollback);
245         ExFreePool(target.Buffer);
246     } else
247         Status = STATUS_SUCCESS;
248 
249     KeQuerySystemTime(&time);
250     win_time_to_unix(time, &now);
251 
252     fcb->inode_item.transid = fcb->Vcb->superblock.generation;
253     fcb->inode_item.sequence++;
254 
255     if (!ccb || !ccb->user_set_change_time)
256         fcb->inode_item.st_ctime = now;
257 
258     if (!ccb || !ccb->user_set_write_time)
259         fcb->inode_item.st_mtime = now;
260 
261     fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
262     fcb->subvol->root_item.ctime = now;
263 
264     fcb->inode_item_changed = true;
265     mark_fcb_dirty(fcb);
266 
267     if (fileref)
268         mark_fileref_dirty(fileref);
269 
270     return Status;
271 }
272 
273 NTSTATUS set_reparse_point2(fcb* fcb, REPARSE_DATA_BUFFER* rdb, ULONG buflen, ccb* ccb, file_ref* fileref, PIRP Irp, LIST_ENTRY* rollback) {
274     NTSTATUS Status;
275     ULONG tag;
276 
277     if (fcb->type == BTRFS_TYPE_SYMLINK) {
278         WARN("tried to set a reparse point on an existing symlink\n");
279         return STATUS_INVALID_PARAMETER;
280     }
281 
282     // FIXME - fail if we already have the attribute FILE_ATTRIBUTE_REPARSE_POINT
283 
284     // FIXME - die if not file or directory
285 
286     if (buflen < sizeof(ULONG)) {
287         WARN("buffer was not long enough to hold tag\n");
288         return STATUS_INVALID_BUFFER_SIZE;
289     }
290 
291     Status = fFsRtlValidateReparsePointBuffer(buflen, rdb);
292     if (!NT_SUCCESS(Status)) {
293         ERR("FsRtlValidateReparsePointBuffer returned %08x\n", Status);
294         return Status;
295     }
296 
297     RtlCopyMemory(&tag, rdb, sizeof(ULONG));
298 
299     if (fcb->type == BTRFS_TYPE_FILE &&
300         ((tag == IO_REPARSE_TAG_SYMLINK && rdb->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE) || tag == IO_REPARSE_TAG_LXSS_SYMLINK)) {
301         Status = set_symlink(Irp, fileref, fcb, ccb, rdb, buflen, tag == IO_REPARSE_TAG_SYMLINK, rollback);
302         fcb->atts |= FILE_ATTRIBUTE_REPARSE_POINT;
303     } else {
304         LARGE_INTEGER offset, time;
305         BTRFS_TIME now;
306 
307         if (fcb->type == BTRFS_TYPE_DIRECTORY || fcb->type == BTRFS_TYPE_CHARDEV || fcb->type == BTRFS_TYPE_BLOCKDEV) { // store as xattr
308             ANSI_STRING buf;
309 
310             buf.Buffer = ExAllocatePoolWithTag(PagedPool, buflen, ALLOC_TAG);
311             if (!buf.Buffer) {
312                 ERR("out of memory\n");
313                 return STATUS_INSUFFICIENT_RESOURCES;
314             }
315             buf.Length = buf.MaximumLength = (uint16_t)buflen;
316 
317             if (fcb->reparse_xattr.Buffer)
318                 ExFreePool(fcb->reparse_xattr.Buffer);
319 
320             fcb->reparse_xattr = buf;
321             RtlCopyMemory(buf.Buffer, rdb, buflen);
322 
323             fcb->reparse_xattr_changed = true;
324 
325             Status = STATUS_SUCCESS;
326         } else { // otherwise, store as file data
327             Status = truncate_file(fcb, 0, Irp, rollback);
328             if (!NT_SUCCESS(Status)) {
329                 ERR("truncate_file returned %08x\n", Status);
330                 return Status;
331             }
332 
333             offset.QuadPart = 0;
334 
335             Status = write_file2(fcb->Vcb, Irp, offset, rdb, &buflen, false, true, true, false, false, rollback);
336             if (!NT_SUCCESS(Status)) {
337                 ERR("write_file2 returned %08x\n", Status);
338                 return Status;
339             }
340         }
341 
342         KeQuerySystemTime(&time);
343         win_time_to_unix(time, &now);
344 
345         fcb->inode_item.transid = fcb->Vcb->superblock.generation;
346         fcb->inode_item.sequence++;
347 
348         if (!ccb || !ccb->user_set_change_time)
349             fcb->inode_item.st_ctime = now;
350 
351         if (!ccb || !ccb->user_set_write_time)
352             fcb->inode_item.st_mtime = now;
353 
354         fcb->atts |= FILE_ATTRIBUTE_REPARSE_POINT;
355         fcb->atts_changed = true;
356 
357         fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
358         fcb->subvol->root_item.ctime = now;
359 
360         fcb->inode_item_changed = true;
361         mark_fcb_dirty(fcb);
362     }
363 
364     return STATUS_SUCCESS;
365 }
366 
367 NTSTATUS set_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
368     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
369     PFILE_OBJECT FileObject = IrpSp->FileObject;
370     void* buffer = Irp->AssociatedIrp.SystemBuffer;
371     REPARSE_DATA_BUFFER* rdb = buffer;
372     DWORD buflen = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
373     NTSTATUS Status = STATUS_SUCCESS;
374     fcb* fcb;
375     ccb* ccb;
376     file_ref* fileref;
377     LIST_ENTRY rollback;
378 
379     TRACE("(%p, %p)\n", DeviceObject, Irp);
380 
381     InitializeListHead(&rollback);
382 
383     if (!FileObject) {
384         ERR("FileObject was NULL\n");
385         return STATUS_INVALID_PARAMETER;
386     }
387 
388     // IFSTest insists on this, for some reason...
389     if (Irp->UserBuffer)
390         return STATUS_INVALID_PARAMETER;
391 
392     fcb = FileObject->FsContext;
393     ccb = FileObject->FsContext2;
394 
395     if (!ccb) {
396         ERR("ccb was NULL\n");
397         return STATUS_INVALID_PARAMETER;
398     }
399 
400     if (Irp->RequestorMode == UserMode && !(ccb->access & (FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA))) {
401         WARN("insufficient privileges\n");
402         return STATUS_ACCESS_DENIED;
403     }
404 
405     fileref = ccb->fileref;
406 
407     if (!fileref) {
408         ERR("fileref was NULL\n");
409         return STATUS_INVALID_PARAMETER;
410     }
411 
412     if (fcb->ads) {
413         fileref = fileref->parent;
414         fcb = fileref->fcb;
415     }
416 
417     ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true);
418     ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);
419 
420     Status = set_reparse_point2(fcb, rdb, buflen, ccb, fileref, Irp, &rollback);
421     if (!NT_SUCCESS(Status)) {
422         ERR("set_reparse_point2 returned %08x\n", Status);
423         goto end;
424     }
425 
426     queue_notification_fcb(fileref, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_ACTION_MODIFIED, NULL);
427 
428 end:
429     if (NT_SUCCESS(Status))
430         clear_rollback(&rollback);
431     else
432         do_rollback(fcb->Vcb, &rollback);
433 
434     ExReleaseResourceLite(fcb->Header.Resource);
435     ExReleaseResourceLite(&fcb->Vcb->tree_lock);
436 
437     return Status;
438 }
439 
440 NTSTATUS delete_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
441     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
442     PFILE_OBJECT FileObject = IrpSp->FileObject;
443     REPARSE_DATA_BUFFER* rdb = Irp->AssociatedIrp.SystemBuffer;
444     DWORD buflen = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
445     NTSTATUS Status;
446     fcb* fcb;
447     ccb* ccb;
448     file_ref* fileref;
449     LIST_ENTRY rollback;
450 
451     TRACE("(%p, %p)\n", DeviceObject, Irp);
452 
453     InitializeListHead(&rollback);
454 
455     if (!FileObject) {
456         ERR("FileObject was NULL\n");
457         return STATUS_INVALID_PARAMETER;
458     }
459 
460     fcb = FileObject->FsContext;
461 
462     if (!fcb) {
463         ERR("fcb was NULL\n");
464         return STATUS_INVALID_PARAMETER;
465     }
466 
467     ccb = FileObject->FsContext2;
468 
469     if (!ccb) {
470         ERR("ccb was NULL\n");
471         return STATUS_INVALID_PARAMETER;
472     }
473 
474     if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_ATTRIBUTES)) {
475         WARN("insufficient privileges\n");
476         return STATUS_ACCESS_DENIED;
477     }
478 
479     fileref = ccb->fileref;
480 
481     if (!fileref) {
482         ERR("fileref was NULL\n");
483         return STATUS_INVALID_PARAMETER;
484     }
485 
486     ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true);
487     ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);
488 
489     if (buflen < offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer)) {
490         ERR("buffer was too short\n");
491         Status = STATUS_INVALID_PARAMETER;
492         goto end;
493     }
494 
495     if (rdb->ReparseDataLength > 0) {
496         WARN("rdb->ReparseDataLength was not zero\n");
497         Status = STATUS_INVALID_PARAMETER;
498         goto end;
499     }
500 
501     if (fcb->ads) {
502         WARN("tried to delete reparse point on ADS\n");
503         Status = STATUS_INVALID_PARAMETER;
504         goto end;
505     }
506 
507     if (fcb->type == BTRFS_TYPE_SYMLINK) {
508         LARGE_INTEGER time;
509         BTRFS_TIME now;
510 
511         if (rdb->ReparseTag != IO_REPARSE_TAG_SYMLINK) {
512             WARN("reparse tag was not IO_REPARSE_TAG_SYMLINK\n");
513             Status = STATUS_INVALID_PARAMETER;
514             goto end;
515         }
516 
517         KeQuerySystemTime(&time);
518         win_time_to_unix(time, &now);
519 
520         fileref->fcb->type = BTRFS_TYPE_FILE;
521         fileref->fcb->inode_item.st_mode &= ~__S_IFLNK;
522         fileref->fcb->inode_item.st_mode |= __S_IFREG;
523         fileref->fcb->inode_item.generation = fileref->fcb->Vcb->superblock.generation; // so we don't confuse btrfs send on Linux
524         fileref->fcb->inode_item.transid = fileref->fcb->Vcb->superblock.generation;
525         fileref->fcb->inode_item.sequence++;
526 
527         if (!ccb->user_set_change_time)
528             fileref->fcb->inode_item.st_ctime = now;
529 
530         if (!ccb->user_set_write_time)
531             fileref->fcb->inode_item.st_mtime = now;
532 
533         fileref->fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;
534 
535         if (fileref->dc)
536             fileref->dc->type = fileref->fcb->type;
537 
538         mark_fileref_dirty(fileref);
539 
540         fileref->fcb->inode_item_changed = true;
541         mark_fcb_dirty(fileref->fcb);
542 
543         fileref->fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
544         fileref->fcb->subvol->root_item.ctime = now;
545     } else if (fcb->type == BTRFS_TYPE_FILE) {
546         LARGE_INTEGER time;
547         BTRFS_TIME now;
548 
549         // FIXME - do we need to check that the reparse tags match?
550 
551         Status = truncate_file(fcb, 0, Irp, &rollback);
552         if (!NT_SUCCESS(Status)) {
553             ERR("truncate_file returned %08x\n", Status);
554             goto end;
555         }
556 
557         fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;
558         fcb->atts_changed = true;
559 
560         KeQuerySystemTime(&time);
561         win_time_to_unix(time, &now);
562 
563         fcb->inode_item.transid = fcb->Vcb->superblock.generation;
564         fcb->inode_item.sequence++;
565 
566         if (!ccb->user_set_change_time)
567             fcb->inode_item.st_ctime = now;
568 
569         if (!ccb->user_set_write_time)
570             fcb->inode_item.st_mtime = now;
571 
572         fcb->inode_item_changed = true;
573         mark_fcb_dirty(fcb);
574 
575         fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
576         fcb->subvol->root_item.ctime = now;
577     } else if (fcb->type == BTRFS_TYPE_DIRECTORY) {
578         LARGE_INTEGER time;
579         BTRFS_TIME now;
580 
581         // FIXME - do we need to check that the reparse tags match?
582 
583         fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;
584         fcb->atts_changed = true;
585 
586         if (fcb->reparse_xattr.Buffer) {
587             ExFreePool(fcb->reparse_xattr.Buffer);
588             fcb->reparse_xattr.Buffer = NULL;
589         }
590 
591         fcb->reparse_xattr_changed = true;
592 
593         KeQuerySystemTime(&time);
594         win_time_to_unix(time, &now);
595 
596         fcb->inode_item.transid = fcb->Vcb->superblock.generation;
597         fcb->inode_item.sequence++;
598 
599         if (!ccb->user_set_change_time)
600             fcb->inode_item.st_ctime = now;
601 
602         if (!ccb->user_set_write_time)
603             fcb->inode_item.st_mtime = now;
604 
605         fcb->inode_item_changed = true;
606         mark_fcb_dirty(fcb);
607 
608         fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
609         fcb->subvol->root_item.ctime = now;
610     } else {
611         ERR("unsupported file type %u\n", fcb->type);
612         Status = STATUS_INVALID_PARAMETER;
613         goto end;
614     }
615 
616     Status = STATUS_SUCCESS;
617 
618     queue_notification_fcb(fileref, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_ACTION_MODIFIED, NULL);
619 
620 end:
621     if (NT_SUCCESS(Status))
622         clear_rollback(&rollback);
623     else
624         do_rollback(fcb->Vcb, &rollback);
625 
626     ExReleaseResourceLite(fcb->Header.Resource);
627     ExReleaseResourceLite(&fcb->Vcb->tree_lock);
628 
629     return Status;
630 }
631