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