1 /* Copyright (c) Mark Harmstone 2016
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
;
29 TRACE("(%p, %p, %p, %x, %p)\n", DeviceObject
, FileObject
, buffer
, buflen
, retlen
);
31 ExAcquireResourceSharedLite(&fcb
->Vcb
->tree_lock
, TRUE
);
32 ExAcquireResourceSharedLite(fcb
->Header
.Resource
, TRUE
);
34 if (fcb
->type
== BTRFS_TYPE_SYMLINK
) {
35 if (called_from_lxss()) {
36 reqlen
= offsetof(REPARSE_DATA_BUFFER
, GenericReparseBuffer
.DataBuffer
) + sizeof(UINT32
);
38 if (buflen
< reqlen
) {
39 Status
= STATUS_BUFFER_OVERFLOW
;
43 rdb
->ReparseTag
= IO_REPARSE_TAG_LXSS_SYMLINK
;
44 rdb
->ReparseDataLength
= offsetof(REPARSE_DATA_BUFFER
, GenericReparseBuffer
.DataBuffer
) + sizeof(UINT32
);
47 *((UINT32
*)rdb
->GenericReparseBuffer
.DataBuffer
) = 1;
51 data
= ExAllocatePoolWithTag(PagedPool
, fcb
->inode_item
.st_size
, ALLOC_TAG
);
53 ERR("out of memory\n");
54 Status
= STATUS_INSUFFICIENT_RESOURCES
;
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
);
61 if (!NT_SUCCESS(Status
)) {
62 ERR("read_file returned %08x\n", Status
);
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
);
74 subnamelen
= stringlen
;
75 printnamelen
= stringlen
;
77 reqlen
= offsetof(REPARSE_DATA_BUFFER
, SymbolicLinkReparseBuffer
.PathBuffer
) + subnamelen
+ printnamelen
;
79 if (buflen
< reqlen
) {
80 Status
= STATUS_BUFFER_OVERFLOW
;
84 rdb
->ReparseTag
= IO_REPARSE_TAG_SYMLINK
;
85 rdb
->ReparseDataLength
= reqlen
- offsetof(REPARSE_DATA_BUFFER
, SymbolicLinkReparseBuffer
);
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
;
94 Status
= RtlUTF8ToUnicodeN(&rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[rdb
->SymbolicLinkReparseBuffer
.SubstituteNameOffset
/ sizeof(WCHAR
)],
95 stringlen
, &stringlen
, data
, fcb
->inode_item
.st_size
);
97 if (!NT_SUCCESS(Status
)) {
98 ERR("RtlUTF8ToUnicodeN 2 returned %08x\n", Status
);
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
] = '\\';
108 RtlCopyMemory(&rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[rdb
->SymbolicLinkReparseBuffer
.PrintNameOffset
/ sizeof(WCHAR
)],
109 &rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[rdb
->SymbolicLinkReparseBuffer
.SubstituteNameOffset
/ sizeof(WCHAR
)],
110 rdb
->SymbolicLinkReparseBuffer
.SubstituteNameLength
);
117 Status
= STATUS_SUCCESS
;
118 } else if (fcb
->atts
& FILE_ATTRIBUTE_REPARSE_POINT
) {
119 if (fcb
->type
== BTRFS_TYPE_FILE
) {
122 Status
= read_file(fcb
, buffer
, 0, buflen
, &len
, NULL
, TRUE
);
124 if (!NT_SUCCESS(Status
)) {
125 ERR("read_file returned %08x\n", Status
);
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
;
136 *retlen
= min(buflen
, fcb
->reparse_xattr
.Length
);
137 RtlCopyMemory(buffer
, fcb
->reparse_xattr
.Buffer
, *retlen
);
141 Status
= STATUS_NOT_A_REPARSE_POINT
;
143 Status
= STATUS_NOT_A_REPARSE_POINT
;
147 ExReleaseResourceLite(fcb
->Header
.Resource
);
148 ExReleaseResourceLite(&fcb
->Vcb
->tree_lock
);
153 static NTSTATUS
set_symlink(PIRP Irp
, file_ref
* fileref
, ccb
* ccb
, REPARSE_DATA_BUFFER
* rdb
, ULONG buflen
, BOOL write
, LIST_ENTRY
* rollback
) {
157 UNICODE_STRING subname
;
159 LARGE_INTEGER offset
, time
;
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
;
170 subname
.Buffer
= &rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[rdb
->SymbolicLinkReparseBuffer
.SubstituteNameOffset
/ sizeof(WCHAR
)];
171 subname
.MaximumLength
= subname
.Length
= rdb
->SymbolicLinkReparseBuffer
.SubstituteNameLength
;
173 TRACE("substitute name = %.*S\n", subname
.Length
/ sizeof(WCHAR
), subname
.Buffer
);
176 fileref
->fcb
->type
= BTRFS_TYPE_SYMLINK
;
178 fileref
->fcb
->inode_item
.st_mode
|= __S_IFLNK
;
181 fileref
->dc
->type
= fileref
->fcb
->type
;
184 Status
= truncate_file(fileref
->fcb
, 0, Irp
, rollback
);
185 if (!NT_SUCCESS(Status
)) {
186 ERR("truncate_file returned %08x\n", Status
);
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
);
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
;
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
);
210 for (i
= 0; i
< target
.Length
; i
++) {
211 if (target
.Buffer
[i
] == '\\')
212 target
.Buffer
[i
] = '/';
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
);
221 Status
= STATUS_SUCCESS
;
223 KeQuerySystemTime(&time
);
224 win_time_to_unix(time
, &now
);
226 fileref
->fcb
->inode_item
.transid
= fileref
->fcb
->Vcb
->superblock
.generation
;
227 fileref
->fcb
->inode_item
.sequence
++;
229 if (!ccb
->user_set_change_time
)
230 fileref
->fcb
->inode_item
.st_ctime
= now
;
232 if (!ccb
->user_set_write_time
)
233 fileref
->fcb
->inode_item
.st_mtime
= now
;
235 fileref
->fcb
->subvol
->root_item
.ctransid
= fileref
->fcb
->Vcb
->superblock
.generation
;
236 fileref
->fcb
->subvol
->root_item
.ctime
= now
;
238 fileref
->fcb
->inode_item_changed
= TRUE
;
239 mark_fcb_dirty(fileref
->fcb
);
241 mark_fileref_dirty(fileref
);
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
;
259 TRACE("(%p, %p)\n", DeviceObject
, Irp
);
261 InitializeListHead(&rollback
);
264 ERR("FileObject was NULL\n");
265 return STATUS_INVALID_PARAMETER
;
268 fcb
= FileObject
->FsContext
;
269 ccb
= FileObject
->FsContext2
;
272 ERR("ccb was NULL\n");
273 return STATUS_INVALID_PARAMETER
;
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
;
283 fileref
= ccb
->fileref
;
286 ERR("fileref was NULL\n");
287 return STATUS_INVALID_PARAMETER
;
290 TRACE("%S\n", file_desc(FileObject
));
292 ExAcquireResourceSharedLite(&fcb
->Vcb
->tree_lock
, TRUE
);
293 ExAcquireResourceExclusiveLite(fcb
->Header
.Resource
, TRUE
);
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
;
301 // FIXME - fail if we already have the attribute FILE_ATTRIBUTE_REPARSE_POINT
303 // FIXME - die if not file or directory
304 // FIXME - die if ADS
306 if (buflen
< sizeof(ULONG
)) {
307 WARN("buffer was not long enough to hold tag\n");
308 Status
= STATUS_INVALID_PARAMETER
;
312 RtlCopyMemory(&tag
, buffer
, sizeof(ULONG
));
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
;
319 LARGE_INTEGER offset
, time
;
322 if (fcb
->type
== BTRFS_TYPE_DIRECTORY
) { // for directories, store as xattr
325 buf
.Buffer
= ExAllocatePoolWithTag(PagedPool
, buflen
, ALLOC_TAG
);
327 ERR("out of memory\n");
328 Status
= STATUS_INSUFFICIENT_RESOURCES
;
331 buf
.Length
= buf
.MaximumLength
= buflen
;
333 if (fcb
->reparse_xattr
.Buffer
)
334 ExFreePool(fcb
->reparse_xattr
.Buffer
);
336 fcb
->reparse_xattr
= buf
;
337 RtlCopyMemory(fcb
->reparse_xattr
.Buffer
, buffer
, buflen
);
339 fcb
->reparse_xattr_changed
= TRUE
;
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
);
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
);
358 KeQuerySystemTime(&time
);
359 win_time_to_unix(time
, &now
);
361 fcb
->inode_item
.transid
= fcb
->Vcb
->superblock
.generation
;
362 fcb
->inode_item
.sequence
++;
364 if (!ccb
->user_set_change_time
)
365 fcb
->inode_item
.st_ctime
= now
;
367 if (!ccb
->user_set_write_time
)
368 fcb
->inode_item
.st_mtime
= now
;
370 fcb
->atts
|= FILE_ATTRIBUTE_REPARSE_POINT
;
371 fcb
->atts_changed
= TRUE
;
373 fcb
->subvol
->root_item
.ctransid
= fcb
->Vcb
->superblock
.generation
;
374 fcb
->subvol
->root_item
.ctime
= now
;
376 fcb
->inode_item_changed
= TRUE
;
380 send_notification_fcb(fileref
, FILE_NOTIFY_CHANGE_LAST_WRITE
| FILE_NOTIFY_CHANGE_ATTRIBUTES
, FILE_ACTION_MODIFIED
);
383 if (NT_SUCCESS(Status
))
384 clear_rollback(fcb
->Vcb
, &rollback
);
386 do_rollback(fcb
->Vcb
, &rollback
);
388 ExReleaseResourceLite(fcb
->Header
.Resource
);
389 ExReleaseResourceLite(&fcb
->Vcb
->tree_lock
);
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
;
405 TRACE("(%p, %p)\n", DeviceObject
, Irp
);
407 InitializeListHead(&rollback
);
410 ERR("FileObject was NULL\n");
411 return STATUS_INVALID_PARAMETER
;
414 fcb
= FileObject
->FsContext
;
417 ERR("fcb was NULL\n");
418 return STATUS_INVALID_PARAMETER
;
421 ccb
= FileObject
->FsContext2
;
424 ERR("ccb was NULL\n");
425 return STATUS_INVALID_PARAMETER
;
428 if (Irp
->RequestorMode
== UserMode
&& !(ccb
->access
& FILE_WRITE_ATTRIBUTES
)) {
429 WARN("insufficient privileges\n");
430 return STATUS_ACCESS_DENIED
;
433 fileref
= ccb
->fileref
;
436 ERR("fileref was NULL\n");
437 Status
= STATUS_INVALID_PARAMETER
;
441 ExAcquireResourceSharedLite(&fcb
->Vcb
->tree_lock
, TRUE
);
442 ExAcquireResourceExclusiveLite(fcb
->Header
.Resource
, TRUE
);
444 TRACE("%S\n", file_desc(FileObject
));
446 if (buflen
< offsetof(REPARSE_DATA_BUFFER
, GenericReparseBuffer
.DataBuffer
)) {
447 ERR("buffer was too short\n");
448 Status
= STATUS_INVALID_PARAMETER
;
452 if (rdb
->ReparseDataLength
> 0) {
453 WARN("rdb->ReparseDataLength was not zero\n");
454 Status
= STATUS_INVALID_PARAMETER
;
459 WARN("tried to delete reparse point on ADS\n");
460 Status
= STATUS_INVALID_PARAMETER
;
464 if (fcb
->type
== BTRFS_TYPE_SYMLINK
) {
468 if (rdb
->ReparseTag
!= IO_REPARSE_TAG_SYMLINK
) {
469 WARN("reparse tag was not IO_REPARSE_TAG_SYMLINK\n");
470 Status
= STATUS_INVALID_PARAMETER
;
474 KeQuerySystemTime(&time
);
475 win_time_to_unix(time
, &now
);
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
++;
483 if (!ccb
->user_set_change_time
)
484 fileref
->fcb
->inode_item
.st_ctime
= now
;
486 if (!ccb
->user_set_write_time
)
487 fileref
->fcb
->inode_item
.st_mtime
= now
;
489 fileref
->fcb
->atts
&= ~FILE_ATTRIBUTE_REPARSE_POINT
;
492 fileref
->dc
->type
= fileref
->fcb
->type
;
494 mark_fileref_dirty(fileref
);
496 fileref
->fcb
->inode_item_changed
= TRUE
;
497 mark_fcb_dirty(fileref
->fcb
);
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
) {
505 // FIXME - do we need to check that the reparse tags match?
507 Status
= truncate_file(fcb
, 0, Irp
, &rollback
);
508 if (!NT_SUCCESS(Status
)) {
509 ERR("truncate_file returned %08x\n", Status
);
513 fcb
->atts
&= ~FILE_ATTRIBUTE_REPARSE_POINT
;
514 fcb
->atts_changed
= TRUE
;
516 KeQuerySystemTime(&time
);
517 win_time_to_unix(time
, &now
);
519 fcb
->inode_item
.transid
= fcb
->Vcb
->superblock
.generation
;
520 fcb
->inode_item
.sequence
++;
522 if (!ccb
->user_set_change_time
)
523 fcb
->inode_item
.st_ctime
= now
;
525 if (!ccb
->user_set_write_time
)
526 fcb
->inode_item
.st_mtime
= now
;
528 fcb
->inode_item_changed
= TRUE
;
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
) {
537 // FIXME - do we need to check that the reparse tags match?
539 fcb
->atts
&= ~FILE_ATTRIBUTE_REPARSE_POINT
;
540 fcb
->atts_changed
= TRUE
;
542 if (fcb
->reparse_xattr
.Buffer
) {
543 ExFreePool(fcb
->reparse_xattr
.Buffer
);
544 fcb
->reparse_xattr
.Buffer
= NULL
;
547 fcb
->reparse_xattr_changed
= TRUE
;
549 KeQuerySystemTime(&time
);
550 win_time_to_unix(time
, &now
);
552 fcb
->inode_item
.transid
= fcb
->Vcb
->superblock
.generation
;
553 fcb
->inode_item
.sequence
++;
555 if (!ccb
->user_set_change_time
)
556 fcb
->inode_item
.st_ctime
= now
;
558 if (!ccb
->user_set_write_time
)
559 fcb
->inode_item
.st_mtime
= now
;
561 fcb
->inode_item_changed
= TRUE
;
564 fcb
->subvol
->root_item
.ctransid
= fcb
->Vcb
->superblock
.generation
;
565 fcb
->subvol
->root_item
.ctime
= now
;
567 ERR("unsupported file type %u\n", fcb
->type
);
568 Status
= STATUS_INVALID_PARAMETER
;
572 Status
= STATUS_SUCCESS
;
574 send_notification_fcb(fileref
, FILE_NOTIFY_CHANGE_LAST_WRITE
| FILE_NOTIFY_CHANGE_ATTRIBUTES
, FILE_ACTION_MODIFIED
);
577 if (NT_SUCCESS(Status
))
578 clear_rollback(fcb
->Vcb
, &rollback
);
580 do_rollback(fcb
->Vcb
, &rollback
);
582 ExReleaseResourceLite(fcb
->Header
.Resource
);
583 ExReleaseResourceLite(&fcb
->Vcb
->tree_lock
);