xref: /reactos/drivers/filesystems/btrfs/reparse.c (revision 0622ce17)
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, 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     fileref->fcb->type = BTRFS_TYPE_SYMLINK;
201     fileref->fcb->inode_item.st_mode |= __S_IFLNK;
202     fileref->fcb->inode_item.generation = fileref->fcb->Vcb->superblock.generation; // so we don't confuse btrfs send on Linux
203 
204     if (fileref->dc)
205         fileref->dc->type = fileref->fcb->type;
206 
207     if (write) {
208         Status = truncate_file(fileref->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(fileref->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     fileref->fcb->inode_item.transid = fileref->fcb->Vcb->superblock.generation;
251     fileref->fcb->inode_item.sequence++;
252 
253     if (!ccb->user_set_change_time)
254         fileref->fcb->inode_item.st_ctime = now;
255 
256     if (!ccb->user_set_write_time)
257         fileref->fcb->inode_item.st_mtime = now;
258 
259     fileref->fcb->subvol->root_item.ctransid = fileref->fcb->Vcb->superblock.generation;
260     fileref->fcb->subvol->root_item.ctime = now;
261 
262     fileref->fcb->inode_item_changed = TRUE;
263     mark_fcb_dirty(fileref->fcb);
264 
265     mark_fileref_dirty(fileref);
266 
267     return Status;
268 }
269 
270 NTSTATUS set_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
271     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
272     PFILE_OBJECT FileObject = IrpSp->FileObject;
273     void* buffer = Irp->AssociatedIrp.SystemBuffer;
274     REPARSE_DATA_BUFFER* rdb = buffer;
275     DWORD buflen = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
276     NTSTATUS Status = STATUS_SUCCESS;
277     fcb* fcb;
278     ccb* ccb;
279     file_ref* fileref;
280     ULONG tag;
281     LIST_ENTRY rollback;
282 
283     TRACE("(%p, %p)\n", DeviceObject, Irp);
284 
285     InitializeListHead(&rollback);
286 
287     if (!FileObject) {
288         ERR("FileObject was NULL\n");
289         return STATUS_INVALID_PARAMETER;
290     }
291 
292     // IFSTest insists on this, for some reason...
293     if (Irp->UserBuffer)
294         return STATUS_INVALID_PARAMETER;
295 
296     fcb = FileObject->FsContext;
297     ccb = FileObject->FsContext2;
298 
299     if (!ccb) {
300         ERR("ccb was NULL\n");
301         return STATUS_INVALID_PARAMETER;
302     }
303 
304     if (Irp->RequestorMode == UserMode && !(ccb->access & (FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA))) {
305         WARN("insufficient privileges\n");
306         return STATUS_ACCESS_DENIED;
307     }
308 
309     fileref = ccb->fileref;
310 
311     if (!fileref) {
312         ERR("fileref was NULL\n");
313         return STATUS_INVALID_PARAMETER;
314     }
315 
316     if (fcb->ads) {
317         fileref = fileref->parent;
318         fcb = fileref->fcb;
319     }
320 
321     TRACE("%S\n", file_desc(FileObject));
322 
323     ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, TRUE);
324     ExAcquireResourceExclusiveLite(fcb->Header.Resource, TRUE);
325 
326     if (fcb->type == BTRFS_TYPE_SYMLINK) {
327         WARN("tried to set a reparse point on an existing symlink\n");
328         Status = STATUS_INVALID_PARAMETER;
329         goto end;
330     }
331 
332     // FIXME - fail if we already have the attribute FILE_ATTRIBUTE_REPARSE_POINT
333 
334     // FIXME - die if not file or directory
335 
336     if (buflen < sizeof(ULONG)) {
337         WARN("buffer was not long enough to hold tag\n");
338         Status = STATUS_INVALID_BUFFER_SIZE;
339         goto end;
340     }
341 
342     Status = FsRtlValidateReparsePointBuffer(buflen, rdb);
343     if (!NT_SUCCESS(Status)) {
344         ERR("FsRtlValidateReparsePointBuffer returned %08x\n", Status);
345         goto end;
346     }
347 
348     RtlCopyMemory(&tag, buffer, sizeof(ULONG));
349 
350     if (fcb->type == BTRFS_TYPE_FILE &&
351         ((tag == IO_REPARSE_TAG_SYMLINK && rdb->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE) || tag == IO_REPARSE_TAG_LXSS_SYMLINK)) {
352         Status = set_symlink(Irp, fileref, ccb, rdb, buflen, tag == IO_REPARSE_TAG_SYMLINK, &rollback);
353         fcb->atts |= FILE_ATTRIBUTE_REPARSE_POINT;
354     } else {
355         LARGE_INTEGER offset, time;
356         BTRFS_TIME now;
357 
358         if (fcb->type == BTRFS_TYPE_DIRECTORY) { // for directories, store as xattr
359             ANSI_STRING buf;
360 
361             buf.Buffer = ExAllocatePoolWithTag(PagedPool, buflen, ALLOC_TAG);
362             if (!buf.Buffer) {
363                 ERR("out of memory\n");
364                 Status = STATUS_INSUFFICIENT_RESOURCES;
365                 goto end;
366             }
367             buf.Length = buf.MaximumLength = (UINT16)buflen;
368 
369             if (fcb->reparse_xattr.Buffer)
370                 ExFreePool(fcb->reparse_xattr.Buffer);
371 
372             fcb->reparse_xattr = buf;
373             RtlCopyMemory(buf.Buffer, buffer, buflen);
374 
375             fcb->reparse_xattr_changed = TRUE;
376 
377             Status = STATUS_SUCCESS;
378         } else { // otherwise, store as file data
379             Status = truncate_file(fcb, 0, Irp, &rollback);
380             if (!NT_SUCCESS(Status)) {
381                 ERR("truncate_file returned %08x\n", Status);
382                 goto end;
383             }
384 
385             offset.QuadPart = 0;
386 
387             Status = write_file2(fcb->Vcb, Irp, offset, buffer, &buflen, FALSE, TRUE, TRUE, FALSE, FALSE, &rollback);
388             if (!NT_SUCCESS(Status)) {
389                 ERR("write_file2 returned %08x\n", Status);
390                 goto end;
391             }
392         }
393 
394         KeQuerySystemTime(&time);
395         win_time_to_unix(time, &now);
396 
397         fcb->inode_item.transid = fcb->Vcb->superblock.generation;
398         fcb->inode_item.sequence++;
399 
400         if (!ccb->user_set_change_time)
401             fcb->inode_item.st_ctime = now;
402 
403         if (!ccb->user_set_write_time)
404             fcb->inode_item.st_mtime = now;
405 
406         fcb->atts |= FILE_ATTRIBUTE_REPARSE_POINT;
407         fcb->atts_changed = TRUE;
408 
409         fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
410         fcb->subvol->root_item.ctime = now;
411 
412         fcb->inode_item_changed = TRUE;
413         mark_fcb_dirty(fcb);
414     }
415 
416     send_notification_fcb(fileref, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_ACTION_MODIFIED, NULL);
417 
418 end:
419     if (NT_SUCCESS(Status))
420         clear_rollback(&rollback);
421     else
422         do_rollback(fcb->Vcb, &rollback);
423 
424     ExReleaseResourceLite(fcb->Header.Resource);
425     ExReleaseResourceLite(&fcb->Vcb->tree_lock);
426 
427     return Status;
428 }
429 
430 NTSTATUS delete_reparse_point(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
431     PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
432     PFILE_OBJECT FileObject = IrpSp->FileObject;
433     REPARSE_DATA_BUFFER* rdb = Irp->AssociatedIrp.SystemBuffer;
434     DWORD buflen = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
435     NTSTATUS Status;
436     fcb* fcb;
437     ccb* ccb;
438     file_ref* fileref;
439     LIST_ENTRY rollback;
440 
441     TRACE("(%p, %p)\n", DeviceObject, Irp);
442 
443     InitializeListHead(&rollback);
444 
445     if (!FileObject) {
446         ERR("FileObject was NULL\n");
447         return STATUS_INVALID_PARAMETER;
448     }
449 
450     fcb = FileObject->FsContext;
451 
452     if (!fcb) {
453         ERR("fcb was NULL\n");
454         return STATUS_INVALID_PARAMETER;
455     }
456 
457     ccb = FileObject->FsContext2;
458 
459     if (!ccb) {
460         ERR("ccb was NULL\n");
461         return STATUS_INVALID_PARAMETER;
462     }
463 
464     if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_ATTRIBUTES)) {
465         WARN("insufficient privileges\n");
466         return STATUS_ACCESS_DENIED;
467     }
468 
469     fileref = ccb->fileref;
470 
471     if (!fileref) {
472         ERR("fileref was NULL\n");
473         return STATUS_INVALID_PARAMETER;
474     }
475 
476     ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, TRUE);
477     ExAcquireResourceExclusiveLite(fcb->Header.Resource, TRUE);
478 
479     TRACE("%S\n", file_desc(FileObject));
480 
481     if (buflen < offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer)) {
482         ERR("buffer was too short\n");
483         Status = STATUS_INVALID_PARAMETER;
484         goto end;
485     }
486 
487     if (rdb->ReparseDataLength > 0) {
488         WARN("rdb->ReparseDataLength was not zero\n");
489         Status = STATUS_INVALID_PARAMETER;
490         goto end;
491     }
492 
493     if (fcb->ads) {
494         WARN("tried to delete reparse point on ADS\n");
495         Status = STATUS_INVALID_PARAMETER;
496         goto end;
497     }
498 
499     if (fcb->type == BTRFS_TYPE_SYMLINK) {
500         LARGE_INTEGER time;
501         BTRFS_TIME now;
502 
503         if (rdb->ReparseTag != IO_REPARSE_TAG_SYMLINK) {
504             WARN("reparse tag was not IO_REPARSE_TAG_SYMLINK\n");
505             Status = STATUS_INVALID_PARAMETER;
506             goto end;
507         }
508 
509         KeQuerySystemTime(&time);
510         win_time_to_unix(time, &now);
511 
512         fileref->fcb->type = BTRFS_TYPE_FILE;
513         fileref->fcb->inode_item.st_mode &= ~__S_IFLNK;
514         fileref->fcb->inode_item.st_mode |= __S_IFREG;
515         fileref->fcb->inode_item.generation = fileref->fcb->Vcb->superblock.generation; // so we don't confuse btrfs send on Linux
516         fileref->fcb->inode_item.transid = fileref->fcb->Vcb->superblock.generation;
517         fileref->fcb->inode_item.sequence++;
518 
519         if (!ccb->user_set_change_time)
520             fileref->fcb->inode_item.st_ctime = now;
521 
522         if (!ccb->user_set_write_time)
523             fileref->fcb->inode_item.st_mtime = now;
524 
525         fileref->fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;
526 
527         if (fileref->dc)
528             fileref->dc->type = fileref->fcb->type;
529 
530         mark_fileref_dirty(fileref);
531 
532         fileref->fcb->inode_item_changed = TRUE;
533         mark_fcb_dirty(fileref->fcb);
534 
535         fileref->fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
536         fileref->fcb->subvol->root_item.ctime = now;
537     } else if (fcb->type == BTRFS_TYPE_FILE) {
538         LARGE_INTEGER time;
539         BTRFS_TIME now;
540 
541         // FIXME - do we need to check that the reparse tags match?
542 
543         Status = truncate_file(fcb, 0, Irp, &rollback);
544         if (!NT_SUCCESS(Status)) {
545             ERR("truncate_file returned %08x\n", Status);
546             goto end;
547         }
548 
549         fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;
550         fcb->atts_changed = TRUE;
551 
552         KeQuerySystemTime(&time);
553         win_time_to_unix(time, &now);
554 
555         fcb->inode_item.transid = fcb->Vcb->superblock.generation;
556         fcb->inode_item.sequence++;
557 
558         if (!ccb->user_set_change_time)
559             fcb->inode_item.st_ctime = now;
560 
561         if (!ccb->user_set_write_time)
562             fcb->inode_item.st_mtime = now;
563 
564         fcb->inode_item_changed = TRUE;
565         mark_fcb_dirty(fcb);
566 
567         fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
568         fcb->subvol->root_item.ctime = now;
569     } else if (fcb->type == BTRFS_TYPE_DIRECTORY) {
570         LARGE_INTEGER time;
571         BTRFS_TIME now;
572 
573         // FIXME - do we need to check that the reparse tags match?
574 
575         fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;
576         fcb->atts_changed = TRUE;
577 
578         if (fcb->reparse_xattr.Buffer) {
579             ExFreePool(fcb->reparse_xattr.Buffer);
580             fcb->reparse_xattr.Buffer = NULL;
581         }
582 
583         fcb->reparse_xattr_changed = TRUE;
584 
585         KeQuerySystemTime(&time);
586         win_time_to_unix(time, &now);
587 
588         fcb->inode_item.transid = fcb->Vcb->superblock.generation;
589         fcb->inode_item.sequence++;
590 
591         if (!ccb->user_set_change_time)
592             fcb->inode_item.st_ctime = now;
593 
594         if (!ccb->user_set_write_time)
595             fcb->inode_item.st_mtime = now;
596 
597         fcb->inode_item_changed = TRUE;
598         mark_fcb_dirty(fcb);
599 
600         fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
601         fcb->subvol->root_item.ctime = now;
602     } else {
603         ERR("unsupported file type %u\n", fcb->type);
604         Status = STATUS_INVALID_PARAMETER;
605         goto end;
606     }
607 
608     Status = STATUS_SUCCESS;
609 
610     send_notification_fcb(fileref, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_ACTION_MODIFIED, NULL);
611 
612 end:
613     if (NT_SUCCESS(Status))
614         clear_rollback(&rollback);
615     else
616         do_rollback(fcb->Vcb, &rollback);
617 
618     ExReleaseResourceLite(fcb->Header.Resource);
619     ExReleaseResourceLite(&fcb->Vcb->tree_lock);
620 
621     return Status;
622 }
623