1 /* Copyright (c) Mark Harmstone 2016-17
3 * This file is part of WinBtrfs.
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.
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.
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/>. */
18 #include "btrfs_drv.h"
20 extern tFsRtlValidateReparsePointBuffer fFsRtlValidateReparsePointBuffer
;
22 NTSTATUS
get_reparse_point(PDEVICE_OBJECT DeviceObject
, PFILE_OBJECT FileObject
, void* buffer
, DWORD buflen
, ULONG_PTR
* retlen
) {
23 USHORT subnamelen
, printnamelen
, i
;
26 REPARSE_DATA_BUFFER
* rdb
= buffer
;
27 fcb
* fcb
= FileObject
->FsContext
;
28 ccb
* ccb
= FileObject
->FsContext2
;
31 TRACE("(%p, %p, %p, %x, %p)\n", DeviceObject
, FileObject
, buffer
, buflen
, retlen
);
34 return STATUS_INVALID_PARAMETER
;
36 ExAcquireResourceSharedLite(&fcb
->Vcb
->tree_lock
, true);
37 ExAcquireResourceSharedLite(fcb
->Header
.Resource
, true);
39 if (fcb
->type
== BTRFS_TYPE_SYMLINK
) {
41 reqlen
= offsetof(REPARSE_DATA_BUFFER
, GenericReparseBuffer
.DataBuffer
) + sizeof(uint32_t);
43 if (buflen
< reqlen
) {
44 Status
= STATUS_BUFFER_OVERFLOW
;
48 rdb
->ReparseTag
= IO_REPARSE_TAG_LXSS_SYMLINK
;
49 rdb
->ReparseDataLength
= offsetof(REPARSE_DATA_BUFFER
, GenericReparseBuffer
.DataBuffer
) + sizeof(uint32_t);
52 *((uint32_t*)rdb
->GenericReparseBuffer
.DataBuffer
) = 1;
58 if (fcb
->inode_item
.st_size
== 0 || fcb
->inode_item
.st_size
> 0xffff) {
59 Status
= STATUS_INVALID_PARAMETER
;
63 data
= ExAllocatePoolWithTag(PagedPool
, (ULONG
)fcb
->inode_item
.st_size
, ALLOC_TAG
);
65 ERR("out of memory\n");
66 Status
= STATUS_INSUFFICIENT_RESOURCES
;
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
);
73 if (!NT_SUCCESS(Status
)) {
74 ERR("read_file returned %08x\n", Status
);
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
);
86 subnamelen
= (uint16_t)stringlen
;
87 printnamelen
= (uint16_t)stringlen
;
89 reqlen
= offsetof(REPARSE_DATA_BUFFER
, SymbolicLinkReparseBuffer
.PathBuffer
) + subnamelen
+ printnamelen
;
91 if (buflen
>= offsetof(REPARSE_DATA_BUFFER
, ReparseDataLength
))
92 rdb
->ReparseTag
= IO_REPARSE_TAG_SYMLINK
;
94 if (buflen
>= offsetof(REPARSE_DATA_BUFFER
, Reserved
))
95 rdb
->ReparseDataLength
= (USHORT
)(reqlen
- offsetof(REPARSE_DATA_BUFFER
, SymbolicLinkReparseBuffer
));
97 if (buflen
>= offsetof(REPARSE_DATA_BUFFER
, SymbolicLinkReparseBuffer
.SubstituteNameOffset
))
100 if (buflen
< reqlen
) {
102 Status
= STATUS_BUFFER_OVERFLOW
;
103 *retlen
= min(buflen
, offsetof(REPARSE_DATA_BUFFER
, SymbolicLinkReparseBuffer
.SubstituteNameOffset
));
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
;
113 Status
= utf8_to_utf16(&rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[rdb
->SymbolicLinkReparseBuffer
.SubstituteNameOffset
/ sizeof(WCHAR
)],
114 stringlen
, &stringlen
, data
, (ULONG
)fcb
->inode_item
.st_size
);
116 if (!NT_SUCCESS(Status
)) {
117 ERR("utf8_to_utf16 2 returned %08x\n", Status
);
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
] = '\\';
127 RtlCopyMemory(&rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[rdb
->SymbolicLinkReparseBuffer
.PrintNameOffset
/ sizeof(WCHAR
)],
128 &rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[rdb
->SymbolicLinkReparseBuffer
.SubstituteNameOffset
/ sizeof(WCHAR
)],
129 rdb
->SymbolicLinkReparseBuffer
.SubstituteNameLength
);
136 Status
= STATUS_SUCCESS
;
137 } else if (fcb
->atts
& FILE_ATTRIBUTE_REPARSE_POINT
) {
138 if (fcb
->type
== BTRFS_TYPE_FILE
) {
141 Status
= read_file(fcb
, buffer
, 0, buflen
, &len
, NULL
);
143 if (!NT_SUCCESS(Status
)) {
144 ERR("read_file returned %08x\n", Status
);
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
;
155 *retlen
= min(buflen
, fcb
->reparse_xattr
.Length
);
156 RtlCopyMemory(buffer
, fcb
->reparse_xattr
.Buffer
, *retlen
);
160 Status
= *retlen
== fcb
->reparse_xattr
.Length
? STATUS_SUCCESS
: STATUS_BUFFER_OVERFLOW
;
162 Status
= STATUS_NOT_A_REPARSE_POINT
;
164 Status
= STATUS_NOT_A_REPARSE_POINT
;
168 ExReleaseResourceLite(fcb
->Header
.Resource
);
169 ExReleaseResourceLite(&fcb
->Vcb
->tree_lock
);
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
) {
178 UNICODE_STRING subname
;
180 LARGE_INTEGER offset
, time
;
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
;
191 if (rdb
->SymbolicLinkReparseBuffer
.SubstituteNameLength
< sizeof(WCHAR
)) {
192 WARN("rdb->SymbolicLinkReparseBuffer.SubstituteNameLength was too short\n");
193 return STATUS_INVALID_PARAMETER
;
196 subname
.Buffer
= &rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[rdb
->SymbolicLinkReparseBuffer
.SubstituteNameOffset
/ sizeof(WCHAR
)];
197 subname
.MaximumLength
= subname
.Length
= rdb
->SymbolicLinkReparseBuffer
.SubstituteNameLength
;
199 TRACE("substitute name = %.*S\n", subname
.Length
/ sizeof(WCHAR
), subname
.Buffer
);
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
206 if (fileref
&& fileref
->dc
)
207 fileref
->dc
->type
= fcb
->type
;
210 Status
= truncate_file(fcb
, 0, Irp
, rollback
);
211 if (!NT_SUCCESS(Status
)) {
212 ERR("truncate_file returned %08x\n", Status
);
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
);
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
;
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
);
236 for (i
= 0; i
< target
.MaximumLength
; i
++) {
237 if (target
.Buffer
[i
] == '\\')
238 target
.Buffer
[i
] = '/';
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
);
247 Status
= STATUS_SUCCESS
;
249 KeQuerySystemTime(&time
);
250 win_time_to_unix(time
, &now
);
252 fcb
->inode_item
.transid
= fcb
->Vcb
->superblock
.generation
;
253 fcb
->inode_item
.sequence
++;
255 if (!ccb
|| !ccb
->user_set_change_time
)
256 fcb
->inode_item
.st_ctime
= now
;
258 if (!ccb
|| !ccb
->user_set_write_time
)
259 fcb
->inode_item
.st_mtime
= now
;
261 fcb
->subvol
->root_item
.ctransid
= fcb
->Vcb
->superblock
.generation
;
262 fcb
->subvol
->root_item
.ctime
= now
;
264 fcb
->inode_item_changed
= true;
268 mark_fileref_dirty(fileref
);
273 NTSTATUS
set_reparse_point2(fcb
* fcb
, REPARSE_DATA_BUFFER
* rdb
, ULONG buflen
, ccb
* ccb
, file_ref
* fileref
, PIRP Irp
, LIST_ENTRY
* rollback
) {
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
;
282 // FIXME - fail if we already have the attribute FILE_ATTRIBUTE_REPARSE_POINT
284 // FIXME - die if not file or directory
286 if (buflen
< sizeof(ULONG
)) {
287 WARN("buffer was not long enough to hold tag\n");
288 return STATUS_INVALID_BUFFER_SIZE
;
291 Status
= fFsRtlValidateReparsePointBuffer(buflen
, rdb
);
292 if (!NT_SUCCESS(Status
)) {
293 ERR("FsRtlValidateReparsePointBuffer returned %08x\n", Status
);
297 RtlCopyMemory(&tag
, rdb
, sizeof(ULONG
));
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
;
304 LARGE_INTEGER offset
, time
;
307 if (fcb
->type
== BTRFS_TYPE_DIRECTORY
|| fcb
->type
== BTRFS_TYPE_CHARDEV
|| fcb
->type
== BTRFS_TYPE_BLOCKDEV
) { // store as xattr
310 buf
.Buffer
= ExAllocatePoolWithTag(PagedPool
, buflen
, ALLOC_TAG
);
312 ERR("out of memory\n");
313 return STATUS_INSUFFICIENT_RESOURCES
;
315 buf
.Length
= buf
.MaximumLength
= (uint16_t)buflen
;
317 if (fcb
->reparse_xattr
.Buffer
)
318 ExFreePool(fcb
->reparse_xattr
.Buffer
);
320 fcb
->reparse_xattr
= buf
;
321 RtlCopyMemory(buf
.Buffer
, rdb
, buflen
);
323 fcb
->reparse_xattr_changed
= true;
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
);
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
);
342 KeQuerySystemTime(&time
);
343 win_time_to_unix(time
, &now
);
345 fcb
->inode_item
.transid
= fcb
->Vcb
->superblock
.generation
;
346 fcb
->inode_item
.sequence
++;
348 if (!ccb
|| !ccb
->user_set_change_time
)
349 fcb
->inode_item
.st_ctime
= now
;
351 if (!ccb
|| !ccb
->user_set_write_time
)
352 fcb
->inode_item
.st_mtime
= now
;
354 fcb
->atts
|= FILE_ATTRIBUTE_REPARSE_POINT
;
355 fcb
->atts_changed
= true;
357 fcb
->subvol
->root_item
.ctransid
= fcb
->Vcb
->superblock
.generation
;
358 fcb
->subvol
->root_item
.ctime
= now
;
360 fcb
->inode_item_changed
= true;
364 return STATUS_SUCCESS
;
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
;
379 TRACE("(%p, %p)\n", DeviceObject
, Irp
);
381 InitializeListHead(&rollback
);
384 ERR("FileObject was NULL\n");
385 return STATUS_INVALID_PARAMETER
;
388 // IFSTest insists on this, for some reason...
390 return STATUS_INVALID_PARAMETER
;
392 fcb
= FileObject
->FsContext
;
393 ccb
= FileObject
->FsContext2
;
396 ERR("ccb was NULL\n");
397 return STATUS_INVALID_PARAMETER
;
400 if (Irp
->RequestorMode
== UserMode
&& !(ccb
->access
& (FILE_WRITE_ATTRIBUTES
| FILE_WRITE_DATA
))) {
401 WARN("insufficient privileges\n");
402 return STATUS_ACCESS_DENIED
;
405 fileref
= ccb
->fileref
;
408 ERR("fileref was NULL\n");
409 return STATUS_INVALID_PARAMETER
;
413 fileref
= fileref
->parent
;
417 ExAcquireResourceSharedLite(&fcb
->Vcb
->tree_lock
, true);
418 ExAcquireResourceExclusiveLite(fcb
->Header
.Resource
, true);
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
);
426 queue_notification_fcb(fileref
, FILE_NOTIFY_CHANGE_LAST_WRITE
| FILE_NOTIFY_CHANGE_ATTRIBUTES
, FILE_ACTION_MODIFIED
, NULL
);
429 if (NT_SUCCESS(Status
))
430 clear_rollback(&rollback
);
432 do_rollback(fcb
->Vcb
, &rollback
);
434 ExReleaseResourceLite(fcb
->Header
.Resource
);
435 ExReleaseResourceLite(&fcb
->Vcb
->tree_lock
);
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
;
451 TRACE("(%p, %p)\n", DeviceObject
, Irp
);
453 InitializeListHead(&rollback
);
456 ERR("FileObject was NULL\n");
457 return STATUS_INVALID_PARAMETER
;
460 fcb
= FileObject
->FsContext
;
463 ERR("fcb was NULL\n");
464 return STATUS_INVALID_PARAMETER
;
467 ccb
= FileObject
->FsContext2
;
470 ERR("ccb was NULL\n");
471 return STATUS_INVALID_PARAMETER
;
474 if (Irp
->RequestorMode
== UserMode
&& !(ccb
->access
& FILE_WRITE_ATTRIBUTES
)) {
475 WARN("insufficient privileges\n");
476 return STATUS_ACCESS_DENIED
;
479 fileref
= ccb
->fileref
;
482 ERR("fileref was NULL\n");
483 return STATUS_INVALID_PARAMETER
;
486 ExAcquireResourceSharedLite(&fcb
->Vcb
->tree_lock
, true);
487 ExAcquireResourceExclusiveLite(fcb
->Header
.Resource
, true);
489 if (buflen
< offsetof(REPARSE_DATA_BUFFER
, GenericReparseBuffer
.DataBuffer
)) {
490 ERR("buffer was too short\n");
491 Status
= STATUS_INVALID_PARAMETER
;
495 if (rdb
->ReparseDataLength
> 0) {
496 WARN("rdb->ReparseDataLength was not zero\n");
497 Status
= STATUS_INVALID_PARAMETER
;
502 WARN("tried to delete reparse point on ADS\n");
503 Status
= STATUS_INVALID_PARAMETER
;
507 if (fcb
->type
== BTRFS_TYPE_SYMLINK
) {
511 if (rdb
->ReparseTag
!= IO_REPARSE_TAG_SYMLINK
) {
512 WARN("reparse tag was not IO_REPARSE_TAG_SYMLINK\n");
513 Status
= STATUS_INVALID_PARAMETER
;
517 KeQuerySystemTime(&time
);
518 win_time_to_unix(time
, &now
);
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
++;
527 if (!ccb
->user_set_change_time
)
528 fileref
->fcb
->inode_item
.st_ctime
= now
;
530 if (!ccb
->user_set_write_time
)
531 fileref
->fcb
->inode_item
.st_mtime
= now
;
533 fileref
->fcb
->atts
&= ~FILE_ATTRIBUTE_REPARSE_POINT
;
536 fileref
->dc
->type
= fileref
->fcb
->type
;
538 mark_fileref_dirty(fileref
);
540 fileref
->fcb
->inode_item_changed
= true;
541 mark_fcb_dirty(fileref
->fcb
);
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
) {
549 // FIXME - do we need to check that the reparse tags match?
551 Status
= truncate_file(fcb
, 0, Irp
, &rollback
);
552 if (!NT_SUCCESS(Status
)) {
553 ERR("truncate_file returned %08x\n", Status
);
557 fcb
->atts
&= ~FILE_ATTRIBUTE_REPARSE_POINT
;
558 fcb
->atts_changed
= true;
560 KeQuerySystemTime(&time
);
561 win_time_to_unix(time
, &now
);
563 fcb
->inode_item
.transid
= fcb
->Vcb
->superblock
.generation
;
564 fcb
->inode_item
.sequence
++;
566 if (!ccb
->user_set_change_time
)
567 fcb
->inode_item
.st_ctime
= now
;
569 if (!ccb
->user_set_write_time
)
570 fcb
->inode_item
.st_mtime
= now
;
572 fcb
->inode_item_changed
= true;
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
) {
581 // FIXME - do we need to check that the reparse tags match?
583 fcb
->atts
&= ~FILE_ATTRIBUTE_REPARSE_POINT
;
584 fcb
->atts_changed
= true;
586 if (fcb
->reparse_xattr
.Buffer
) {
587 ExFreePool(fcb
->reparse_xattr
.Buffer
);
588 fcb
->reparse_xattr
.Buffer
= NULL
;
591 fcb
->reparse_xattr_changed
= true;
593 KeQuerySystemTime(&time
);
594 win_time_to_unix(time
, &now
);
596 fcb
->inode_item
.transid
= fcb
->Vcb
->superblock
.generation
;
597 fcb
->inode_item
.sequence
++;
599 if (!ccb
->user_set_change_time
)
600 fcb
->inode_item
.st_ctime
= now
;
602 if (!ccb
->user_set_write_time
)
603 fcb
->inode_item
.st_mtime
= now
;
605 fcb
->inode_item_changed
= true;
608 fcb
->subvol
->root_item
.ctransid
= fcb
->Vcb
->superblock
.generation
;
609 fcb
->subvol
->root_item
.ctime
= now
;
611 ERR("unsupported file type %u\n", fcb
->type
);
612 Status
= STATUS_INVALID_PARAMETER
;
616 Status
= STATUS_SUCCESS
;
618 queue_notification_fcb(fileref
, FILE_NOTIFY_CHANGE_LAST_WRITE
| FILE_NOTIFY_CHANGE_ATTRIBUTES
, FILE_ACTION_MODIFIED
, NULL
);
621 if (NT_SUCCESS(Status
))
622 clear_rollback(&rollback
);
624 do_rollback(fcb
->Vcb
, &rollback
);
626 ExReleaseResourceLite(fcb
->Header
.Resource
);
627 ExReleaseResourceLite(&fcb
->Vcb
->tree_lock
);