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