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 NTSTATUS
get_reparse_point(PDEVICE_OBJECT DeviceObject
, PFILE_OBJECT FileObject
, void* buffer
, DWORD buflen
, ULONG_PTR
* retlen
) {
21 USHORT subnamelen
, printnamelen
, i
;
24 REPARSE_DATA_BUFFER
* rdb
= buffer
;
25 fcb
* fcb
= FileObject
->FsContext
;
26 ccb
* ccb
= FileObject
->FsContext2
;
29 TRACE("(%p, %p, %p, %x, %p)\n", DeviceObject
, FileObject
, buffer
, buflen
, retlen
);
32 return STATUS_INVALID_PARAMETER
;
34 ExAcquireResourceSharedLite(&fcb
->Vcb
->tree_lock
, TRUE
);
35 ExAcquireResourceSharedLite(fcb
->Header
.Resource
, TRUE
);
37 if (fcb
->type
== BTRFS_TYPE_SYMLINK
) {
39 reqlen
= offsetof(REPARSE_DATA_BUFFER
, GenericReparseBuffer
.DataBuffer
) + sizeof(UINT32
);
41 if (buflen
< reqlen
) {
42 Status
= STATUS_BUFFER_OVERFLOW
;
46 rdb
->ReparseTag
= IO_REPARSE_TAG_LXSS_SYMLINK
;
47 rdb
->ReparseDataLength
= offsetof(REPARSE_DATA_BUFFER
, GenericReparseBuffer
.DataBuffer
) + sizeof(UINT32
);
50 *((UINT32
*)rdb
->GenericReparseBuffer
.DataBuffer
) = 1;
56 if (fcb
->inode_item
.st_size
== 0 || fcb
->inode_item
.st_size
> 0xffff) {
57 Status
= STATUS_INVALID_PARAMETER
;
61 data
= ExAllocatePoolWithTag(PagedPool
, (ULONG
)fcb
->inode_item
.st_size
, ALLOC_TAG
);
63 ERR("out of memory\n");
64 Status
= STATUS_INSUFFICIENT_RESOURCES
;
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
);
71 if (!NT_SUCCESS(Status
)) {
72 ERR("read_file returned %08x\n", Status
);
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
);
84 subnamelen
= (UINT16
)stringlen
;
85 printnamelen
= (UINT16
)stringlen
;
87 reqlen
= offsetof(REPARSE_DATA_BUFFER
, SymbolicLinkReparseBuffer
.PathBuffer
) + subnamelen
+ printnamelen
;
89 if (buflen
>= offsetof(REPARSE_DATA_BUFFER
, ReparseDataLength
))
90 rdb
->ReparseTag
= IO_REPARSE_TAG_SYMLINK
;
92 if (buflen
>= offsetof(REPARSE_DATA_BUFFER
, Reserved
))
93 rdb
->ReparseDataLength
= (USHORT
)(reqlen
- offsetof(REPARSE_DATA_BUFFER
, SymbolicLinkReparseBuffer
));
95 if (buflen
>= offsetof(REPARSE_DATA_BUFFER
, SymbolicLinkReparseBuffer
.SubstituteNameOffset
))
98 if (buflen
< reqlen
) {
100 Status
= STATUS_BUFFER_OVERFLOW
;
101 *retlen
= min(buflen
, offsetof(REPARSE_DATA_BUFFER
, SymbolicLinkReparseBuffer
.SubstituteNameOffset
));
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
;
111 Status
= RtlUTF8ToUnicodeN(&rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[rdb
->SymbolicLinkReparseBuffer
.SubstituteNameOffset
/ sizeof(WCHAR
)],
112 stringlen
, &stringlen
, data
, (ULONG
)fcb
->inode_item
.st_size
);
114 if (!NT_SUCCESS(Status
)) {
115 ERR("RtlUTF8ToUnicodeN 2 returned %08x\n", Status
);
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
] = '\\';
125 RtlCopyMemory(&rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[rdb
->SymbolicLinkReparseBuffer
.PrintNameOffset
/ sizeof(WCHAR
)],
126 &rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[rdb
->SymbolicLinkReparseBuffer
.SubstituteNameOffset
/ sizeof(WCHAR
)],
127 rdb
->SymbolicLinkReparseBuffer
.SubstituteNameLength
);
134 Status
= STATUS_SUCCESS
;
135 } else if (fcb
->atts
& FILE_ATTRIBUTE_REPARSE_POINT
) {
136 if (fcb
->type
== BTRFS_TYPE_FILE
) {
139 Status
= read_file(fcb
, buffer
, 0, buflen
, &len
, NULL
);
141 if (!NT_SUCCESS(Status
)) {
142 ERR("read_file returned %08x\n", Status
);
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
;
153 *retlen
= min(buflen
, fcb
->reparse_xattr
.Length
);
154 RtlCopyMemory(buffer
, fcb
->reparse_xattr
.Buffer
, *retlen
);
158 Status
= *retlen
== fcb
->reparse_xattr
.Length
? STATUS_SUCCESS
: STATUS_BUFFER_OVERFLOW
;
160 Status
= STATUS_NOT_A_REPARSE_POINT
;
162 Status
= STATUS_NOT_A_REPARSE_POINT
;
166 ExReleaseResourceLite(fcb
->Header
.Resource
);
167 ExReleaseResourceLite(&fcb
->Vcb
->tree_lock
);
172 static NTSTATUS
set_symlink(PIRP Irp
, file_ref
* fileref
, ccb
* ccb
, REPARSE_DATA_BUFFER
* rdb
, ULONG buflen
, BOOL write
, LIST_ENTRY
* rollback
) {
176 UNICODE_STRING subname
;
178 LARGE_INTEGER offset
, time
;
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
;
189 if (rdb
->SymbolicLinkReparseBuffer
.SubstituteNameLength
< sizeof(WCHAR
)) {
190 WARN("rdb->SymbolicLinkReparseBuffer.SubstituteNameLength was too short\n");
191 return STATUS_INVALID_PARAMETER
;
194 subname
.Buffer
= &rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[rdb
->SymbolicLinkReparseBuffer
.SubstituteNameOffset
/ sizeof(WCHAR
)];
195 subname
.MaximumLength
= subname
.Length
= rdb
->SymbolicLinkReparseBuffer
.SubstituteNameLength
;
197 TRACE("substitute name = %.*S\n", subname
.Length
/ sizeof(WCHAR
), subname
.Buffer
);
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
205 fileref
->dc
->type
= fileref
->fcb
->type
;
208 Status
= truncate_file(fileref
->fcb
, 0, Irp
, rollback
);
209 if (!NT_SUCCESS(Status
)) {
210 ERR("truncate_file returned %08x\n", Status
);
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
);
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
;
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
);
234 for (i
= 0; i
< target
.MaximumLength
; i
++) {
235 if (target
.Buffer
[i
] == '\\')
236 target
.Buffer
[i
] = '/';
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
);
245 Status
= STATUS_SUCCESS
;
247 KeQuerySystemTime(&time
);
248 win_time_to_unix(time
, &now
);
250 fileref
->fcb
->inode_item
.transid
= fileref
->fcb
->Vcb
->superblock
.generation
;
251 fileref
->fcb
->inode_item
.sequence
++;
253 if (!ccb
->user_set_change_time
)
254 fileref
->fcb
->inode_item
.st_ctime
= now
;
256 if (!ccb
->user_set_write_time
)
257 fileref
->fcb
->inode_item
.st_mtime
= now
;
259 fileref
->fcb
->subvol
->root_item
.ctransid
= fileref
->fcb
->Vcb
->superblock
.generation
;
260 fileref
->fcb
->subvol
->root_item
.ctime
= now
;
262 fileref
->fcb
->inode_item_changed
= TRUE
;
263 mark_fcb_dirty(fileref
->fcb
);
265 mark_fileref_dirty(fileref
);
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
;
283 TRACE("(%p, %p)\n", DeviceObject
, Irp
);
285 InitializeListHead(&rollback
);
288 ERR("FileObject was NULL\n");
289 return STATUS_INVALID_PARAMETER
;
292 // IFSTest insists on this, for some reason...
294 return STATUS_INVALID_PARAMETER
;
296 fcb
= FileObject
->FsContext
;
297 ccb
= FileObject
->FsContext2
;
300 ERR("ccb was NULL\n");
301 return STATUS_INVALID_PARAMETER
;
304 if (Irp
->RequestorMode
== UserMode
&& !(ccb
->access
& (FILE_WRITE_ATTRIBUTES
| FILE_WRITE_DATA
))) {
305 WARN("insufficient privileges\n");
306 return STATUS_ACCESS_DENIED
;
309 fileref
= ccb
->fileref
;
312 ERR("fileref was NULL\n");
313 return STATUS_INVALID_PARAMETER
;
317 fileref
= fileref
->parent
;
321 TRACE("%S\n", file_desc(FileObject
));
323 ExAcquireResourceSharedLite(&fcb
->Vcb
->tree_lock
, TRUE
);
324 ExAcquireResourceExclusiveLite(fcb
->Header
.Resource
, TRUE
);
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
;
332 // FIXME - fail if we already have the attribute FILE_ATTRIBUTE_REPARSE_POINT
334 // FIXME - die if not file or directory
336 if (buflen
< sizeof(ULONG
)) {
337 WARN("buffer was not long enough to hold tag\n");
338 Status
= STATUS_INVALID_BUFFER_SIZE
;
342 Status
= FsRtlValidateReparsePointBuffer(buflen
, rdb
);
343 if (!NT_SUCCESS(Status
)) {
344 ERR("FsRtlValidateReparsePointBuffer returned %08x\n", Status
);
348 RtlCopyMemory(&tag
, buffer
, sizeof(ULONG
));
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
;
355 LARGE_INTEGER offset
, time
;
358 if (fcb
->type
== BTRFS_TYPE_DIRECTORY
) { // for directories, store as xattr
361 buf
.Buffer
= ExAllocatePoolWithTag(PagedPool
, buflen
, ALLOC_TAG
);
363 ERR("out of memory\n");
364 Status
= STATUS_INSUFFICIENT_RESOURCES
;
367 buf
.Length
= buf
.MaximumLength
= (UINT16
)buflen
;
369 if (fcb
->reparse_xattr
.Buffer
)
370 ExFreePool(fcb
->reparse_xattr
.Buffer
);
372 fcb
->reparse_xattr
= buf
;
373 RtlCopyMemory(buf
.Buffer
, buffer
, buflen
);
375 fcb
->reparse_xattr_changed
= TRUE
;
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
);
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
);
394 KeQuerySystemTime(&time
);
395 win_time_to_unix(time
, &now
);
397 fcb
->inode_item
.transid
= fcb
->Vcb
->superblock
.generation
;
398 fcb
->inode_item
.sequence
++;
400 if (!ccb
->user_set_change_time
)
401 fcb
->inode_item
.st_ctime
= now
;
403 if (!ccb
->user_set_write_time
)
404 fcb
->inode_item
.st_mtime
= now
;
406 fcb
->atts
|= FILE_ATTRIBUTE_REPARSE_POINT
;
407 fcb
->atts_changed
= TRUE
;
409 fcb
->subvol
->root_item
.ctransid
= fcb
->Vcb
->superblock
.generation
;
410 fcb
->subvol
->root_item
.ctime
= now
;
412 fcb
->inode_item_changed
= TRUE
;
416 send_notification_fcb(fileref
, FILE_NOTIFY_CHANGE_LAST_WRITE
| FILE_NOTIFY_CHANGE_ATTRIBUTES
, FILE_ACTION_MODIFIED
, NULL
);
419 if (NT_SUCCESS(Status
))
420 clear_rollback(&rollback
);
422 do_rollback(fcb
->Vcb
, &rollback
);
424 ExReleaseResourceLite(fcb
->Header
.Resource
);
425 ExReleaseResourceLite(&fcb
->Vcb
->tree_lock
);
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
;
441 TRACE("(%p, %p)\n", DeviceObject
, Irp
);
443 InitializeListHead(&rollback
);
446 ERR("FileObject was NULL\n");
447 return STATUS_INVALID_PARAMETER
;
450 fcb
= FileObject
->FsContext
;
453 ERR("fcb was NULL\n");
454 return STATUS_INVALID_PARAMETER
;
457 ccb
= FileObject
->FsContext2
;
460 ERR("ccb was NULL\n");
461 return STATUS_INVALID_PARAMETER
;
464 if (Irp
->RequestorMode
== UserMode
&& !(ccb
->access
& FILE_WRITE_ATTRIBUTES
)) {
465 WARN("insufficient privileges\n");
466 return STATUS_ACCESS_DENIED
;
469 fileref
= ccb
->fileref
;
472 ERR("fileref was NULL\n");
473 return STATUS_INVALID_PARAMETER
;
476 ExAcquireResourceSharedLite(&fcb
->Vcb
->tree_lock
, TRUE
);
477 ExAcquireResourceExclusiveLite(fcb
->Header
.Resource
, TRUE
);
479 TRACE("%S\n", file_desc(FileObject
));
481 if (buflen
< offsetof(REPARSE_DATA_BUFFER
, GenericReparseBuffer
.DataBuffer
)) {
482 ERR("buffer was too short\n");
483 Status
= STATUS_INVALID_PARAMETER
;
487 if (rdb
->ReparseDataLength
> 0) {
488 WARN("rdb->ReparseDataLength was not zero\n");
489 Status
= STATUS_INVALID_PARAMETER
;
494 WARN("tried to delete reparse point on ADS\n");
495 Status
= STATUS_INVALID_PARAMETER
;
499 if (fcb
->type
== BTRFS_TYPE_SYMLINK
) {
503 if (rdb
->ReparseTag
!= IO_REPARSE_TAG_SYMLINK
) {
504 WARN("reparse tag was not IO_REPARSE_TAG_SYMLINK\n");
505 Status
= STATUS_INVALID_PARAMETER
;
509 KeQuerySystemTime(&time
);
510 win_time_to_unix(time
, &now
);
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
++;
519 if (!ccb
->user_set_change_time
)
520 fileref
->fcb
->inode_item
.st_ctime
= now
;
522 if (!ccb
->user_set_write_time
)
523 fileref
->fcb
->inode_item
.st_mtime
= now
;
525 fileref
->fcb
->atts
&= ~FILE_ATTRIBUTE_REPARSE_POINT
;
528 fileref
->dc
->type
= fileref
->fcb
->type
;
530 mark_fileref_dirty(fileref
);
532 fileref
->fcb
->inode_item_changed
= TRUE
;
533 mark_fcb_dirty(fileref
->fcb
);
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
) {
541 // FIXME - do we need to check that the reparse tags match?
543 Status
= truncate_file(fcb
, 0, Irp
, &rollback
);
544 if (!NT_SUCCESS(Status
)) {
545 ERR("truncate_file returned %08x\n", Status
);
549 fcb
->atts
&= ~FILE_ATTRIBUTE_REPARSE_POINT
;
550 fcb
->atts_changed
= TRUE
;
552 KeQuerySystemTime(&time
);
553 win_time_to_unix(time
, &now
);
555 fcb
->inode_item
.transid
= fcb
->Vcb
->superblock
.generation
;
556 fcb
->inode_item
.sequence
++;
558 if (!ccb
->user_set_change_time
)
559 fcb
->inode_item
.st_ctime
= now
;
561 if (!ccb
->user_set_write_time
)
562 fcb
->inode_item
.st_mtime
= now
;
564 fcb
->inode_item_changed
= TRUE
;
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
) {
573 // FIXME - do we need to check that the reparse tags match?
575 fcb
->atts
&= ~FILE_ATTRIBUTE_REPARSE_POINT
;
576 fcb
->atts_changed
= TRUE
;
578 if (fcb
->reparse_xattr
.Buffer
) {
579 ExFreePool(fcb
->reparse_xattr
.Buffer
);
580 fcb
->reparse_xattr
.Buffer
= NULL
;
583 fcb
->reparse_xattr_changed
= TRUE
;
585 KeQuerySystemTime(&time
);
586 win_time_to_unix(time
, &now
);
588 fcb
->inode_item
.transid
= fcb
->Vcb
->superblock
.generation
;
589 fcb
->inode_item
.sequence
++;
591 if (!ccb
->user_set_change_time
)
592 fcb
->inode_item
.st_ctime
= now
;
594 if (!ccb
->user_set_write_time
)
595 fcb
->inode_item
.st_mtime
= now
;
597 fcb
->inode_item_changed
= TRUE
;
600 fcb
->subvol
->root_item
.ctransid
= fcb
->Vcb
->superblock
.generation
;
601 fcb
->subvol
->root_item
.ctime
= now
;
603 ERR("unsupported file type %u\n", fcb
->type
);
604 Status
= STATUS_INVALID_PARAMETER
;
608 Status
= STATUS_SUCCESS
;
610 send_notification_fcb(fileref
, FILE_NOTIFY_CHANGE_LAST_WRITE
| FILE_NOTIFY_CHANGE_ATTRIBUTES
, FILE_ACTION_MODIFIED
, NULL
);
613 if (NT_SUCCESS(Status
))
614 clear_rollback(&rollback
);
616 do_rollback(fcb
->Vcb
, &rollback
);
618 ExReleaseResourceLite(fcb
->Header
.Resource
);
619 ExReleaseResourceLite(&fcb
->Vcb
->tree_lock
);