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