xref: /reactos/drivers/filesystems/btrfs/reparse.c (revision 62919904)
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     TRACE("%S\n", file_desc(FileObject));
418 
419     ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true);
420     ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);
421 
422     Status = set_reparse_point2(fcb, rdb, buflen, ccb, fileref, Irp, &rollback);
423     if (!NT_SUCCESS(Status)) {
424         ERR("set_reparse_point2 returned %08x\n", Status);
425         goto end;
426     }
427 
428     send_notification_fcb(fileref, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_ACTION_MODIFIED, NULL);
429 
430 end:
431     if (NT_SUCCESS(Status))
432         clear_rollback(&rollback);
433     else
434         do_rollback(fcb->Vcb, &rollback);
435 
436     ExReleaseResourceLite(fcb->Header.Resource);
437     ExReleaseResourceLite(&fcb->Vcb->tree_lock);
438 
439     return Status;
440 }
441 
442 NTSTATUS delete_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
443     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
444     PFILE_OBJECT FileObject = IrpSp->FileObject;
445     REPARSE_DATA_BUFFER* rdb = Irp->AssociatedIrp.SystemBuffer;
446     DWORD buflen = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
447     NTSTATUS Status;
448     fcb* fcb;
449     ccb* ccb;
450     file_ref* fileref;
451     LIST_ENTRY rollback;
452 
453     TRACE("(%p, %p)\n", DeviceObject, Irp);
454 
455     InitializeListHead(&rollback);
456 
457     if (!FileObject) {
458         ERR("FileObject was NULL\n");
459         return STATUS_INVALID_PARAMETER;
460     }
461 
462     fcb = FileObject->FsContext;
463 
464     if (!fcb) {
465         ERR("fcb was NULL\n");
466         return STATUS_INVALID_PARAMETER;
467     }
468 
469     ccb = FileObject->FsContext2;
470 
471     if (!ccb) {
472         ERR("ccb was NULL\n");
473         return STATUS_INVALID_PARAMETER;
474     }
475 
476     if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_ATTRIBUTES)) {
477         WARN("insufficient privileges\n");
478         return STATUS_ACCESS_DENIED;
479     }
480 
481     fileref = ccb->fileref;
482 
483     if (!fileref) {
484         ERR("fileref was NULL\n");
485         return STATUS_INVALID_PARAMETER;
486     }
487 
488     ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true);
489     ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);
490 
491     TRACE("%S\n", file_desc(FileObject));
492 
493     if (buflen < offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer)) {
494         ERR("buffer was too short\n");
495         Status = STATUS_INVALID_PARAMETER;
496         goto end;
497     }
498 
499     if (rdb->ReparseDataLength > 0) {
500         WARN("rdb->ReparseDataLength was not zero\n");
501         Status = STATUS_INVALID_PARAMETER;
502         goto end;
503     }
504 
505     if (fcb->ads) {
506         WARN("tried to delete reparse point on ADS\n");
507         Status = STATUS_INVALID_PARAMETER;
508         goto end;
509     }
510 
511     if (fcb->type == BTRFS_TYPE_SYMLINK) {
512         LARGE_INTEGER time;
513         BTRFS_TIME now;
514 
515         if (rdb->ReparseTag != IO_REPARSE_TAG_SYMLINK) {
516             WARN("reparse tag was not IO_REPARSE_TAG_SYMLINK\n");
517             Status = STATUS_INVALID_PARAMETER;
518             goto end;
519         }
520 
521         KeQuerySystemTime(&time);
522         win_time_to_unix(time, &now);
523 
524         fileref->fcb->type = BTRFS_TYPE_FILE;
525         fileref->fcb->inode_item.st_mode &= ~__S_IFLNK;
526         fileref->fcb->inode_item.st_mode |= __S_IFREG;
527         fileref->fcb->inode_item.generation = fileref->fcb->Vcb->superblock.generation; // so we don't confuse btrfs send on Linux
528         fileref->fcb->inode_item.transid = fileref->fcb->Vcb->superblock.generation;
529         fileref->fcb->inode_item.sequence++;
530 
531         if (!ccb->user_set_change_time)
532             fileref->fcb->inode_item.st_ctime = now;
533 
534         if (!ccb->user_set_write_time)
535             fileref->fcb->inode_item.st_mtime = now;
536 
537         fileref->fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;
538 
539         if (fileref->dc)
540             fileref->dc->type = fileref->fcb->type;
541 
542         mark_fileref_dirty(fileref);
543 
544         fileref->fcb->inode_item_changed = true;
545         mark_fcb_dirty(fileref->fcb);
546 
547         fileref->fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
548         fileref->fcb->subvol->root_item.ctime = now;
549     } else if (fcb->type == BTRFS_TYPE_FILE) {
550         LARGE_INTEGER time;
551         BTRFS_TIME now;
552 
553         // FIXME - do we need to check that the reparse tags match?
554 
555         Status = truncate_file(fcb, 0, Irp, &rollback);
556         if (!NT_SUCCESS(Status)) {
557             ERR("truncate_file returned %08x\n", Status);
558             goto end;
559         }
560 
561         fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;
562         fcb->atts_changed = true;
563 
564         KeQuerySystemTime(&time);
565         win_time_to_unix(time, &now);
566 
567         fcb->inode_item.transid = fcb->Vcb->superblock.generation;
568         fcb->inode_item.sequence++;
569 
570         if (!ccb->user_set_change_time)
571             fcb->inode_item.st_ctime = now;
572 
573         if (!ccb->user_set_write_time)
574             fcb->inode_item.st_mtime = now;
575 
576         fcb->inode_item_changed = true;
577         mark_fcb_dirty(fcb);
578 
579         fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
580         fcb->subvol->root_item.ctime = now;
581     } else if (fcb->type == BTRFS_TYPE_DIRECTORY) {
582         LARGE_INTEGER time;
583         BTRFS_TIME now;
584 
585         // FIXME - do we need to check that the reparse tags match?
586 
587         fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;
588         fcb->atts_changed = true;
589 
590         if (fcb->reparse_xattr.Buffer) {
591             ExFreePool(fcb->reparse_xattr.Buffer);
592             fcb->reparse_xattr.Buffer = NULL;
593         }
594 
595         fcb->reparse_xattr_changed = true;
596 
597         KeQuerySystemTime(&time);
598         win_time_to_unix(time, &now);
599 
600         fcb->inode_item.transid = fcb->Vcb->superblock.generation;
601         fcb->inode_item.sequence++;
602 
603         if (!ccb->user_set_change_time)
604             fcb->inode_item.st_ctime = now;
605 
606         if (!ccb->user_set_write_time)
607             fcb->inode_item.st_mtime = now;
608 
609         fcb->inode_item_changed = true;
610         mark_fcb_dirty(fcb);
611 
612         fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
613         fcb->subvol->root_item.ctime = now;
614     } else {
615         ERR("unsupported file type %u\n", fcb->type);
616         Status = STATUS_INVALID_PARAMETER;
617         goto end;
618     }
619 
620     Status = STATUS_SUCCESS;
621 
622     send_notification_fcb(fileref, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_ACTION_MODIFIED, NULL);
623 
624 end:
625     if (NT_SUCCESS(Status))
626         clear_rollback(&rollback);
627     else
628         do_rollback(fcb->Vcb, &rollback);
629 
630     ExReleaseResourceLite(fcb->Header.Resource);
631     ExReleaseResourceLite(&fcb->Vcb->tree_lock);
632 
633     return Status;
634 }
635