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
, DWORD
* retlen
) {
21 USHORT subnamelen
, printnamelen
, i
;
24 REPARSE_DATA_BUFFER
* rdb
= buffer
;
25 fcb
* fcb
= FileObject
->FsContext
;
29 // FIXME - check permissions
31 TRACE("(%p, %p, %p, %x, %p)\n", DeviceObject
, FileObject
, buffer
, buflen
, retlen
);
33 ExAcquireResourceSharedLite(&fcb
->Vcb
->tree_lock
, TRUE
);
34 ExAcquireResourceSharedLite(fcb
->Header
.Resource
, TRUE
);
36 if (fcb
->type
== BTRFS_TYPE_SYMLINK
) {
37 data
= ExAllocatePoolWithTag(PagedPool
, fcb
->inode_item
.st_size
, ALLOC_TAG
);
39 ERR("out of memory\n");
40 Status
= STATUS_INSUFFICIENT_RESOURCES
;
44 TRACE("data = %p, size = %x\n", data
, fcb
->inode_item
.st_size
);
45 Status
= read_file(fcb
, (UINT8
*)data
, 0, fcb
->inode_item
.st_size
, NULL
, NULL
);
47 if (!NT_SUCCESS(Status
)) {
48 ERR("read_file returned %08x\n", Status
);
53 Status
= RtlUTF8ToUnicodeN(NULL
, 0, &stringlen
, data
, fcb
->inode_item
.st_size
);
54 if (!NT_SUCCESS(Status
)) {
55 ERR("RtlUTF8ToUnicodeN 1 returned %08x\n", Status
);
60 subnamelen
= stringlen
;
61 printnamelen
= stringlen
;
63 reqlen
= offsetof(REPARSE_DATA_BUFFER
, SymbolicLinkReparseBuffer
.PathBuffer
) + subnamelen
+ printnamelen
;
65 if (buflen
< reqlen
) {
66 Status
= STATUS_BUFFER_OVERFLOW
;
70 rdb
->ReparseTag
= IO_REPARSE_TAG_SYMLINK
;
71 rdb
->ReparseDataLength
= reqlen
- offsetof(REPARSE_DATA_BUFFER
, SymbolicLinkReparseBuffer
);
74 rdb
->SymbolicLinkReparseBuffer
.SubstituteNameOffset
= 0;
75 rdb
->SymbolicLinkReparseBuffer
.SubstituteNameLength
= subnamelen
;
76 rdb
->SymbolicLinkReparseBuffer
.PrintNameOffset
= subnamelen
;
77 rdb
->SymbolicLinkReparseBuffer
.PrintNameLength
= printnamelen
;
78 rdb
->SymbolicLinkReparseBuffer
.Flags
= SYMLINK_FLAG_RELATIVE
;
80 Status
= RtlUTF8ToUnicodeN(&rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[rdb
->SymbolicLinkReparseBuffer
.SubstituteNameOffset
/ sizeof(WCHAR
)],
81 stringlen
, &stringlen
, data
, fcb
->inode_item
.st_size
);
83 if (!NT_SUCCESS(Status
)) {
84 ERR("RtlUTF8ToUnicodeN 2 returned %08x\n", Status
);
89 for (i
= 0; i
< stringlen
/ sizeof(WCHAR
); i
++) {
90 if (rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[(rdb
->SymbolicLinkReparseBuffer
.SubstituteNameOffset
/ sizeof(WCHAR
)) + i
] == '/')
91 rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[(rdb
->SymbolicLinkReparseBuffer
.SubstituteNameOffset
/ sizeof(WCHAR
)) + i
] = '\\';
94 RtlCopyMemory(&rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[rdb
->SymbolicLinkReparseBuffer
.PrintNameOffset
/ sizeof(WCHAR
)],
95 &rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[rdb
->SymbolicLinkReparseBuffer
.SubstituteNameOffset
/ sizeof(WCHAR
)],
96 rdb
->SymbolicLinkReparseBuffer
.SubstituteNameLength
);
102 Status
= STATUS_SUCCESS
;
103 } else if (fcb
->atts
& FILE_ATTRIBUTE_REPARSE_POINT
) {
104 if (fcb
->type
== BTRFS_TYPE_FILE
) {
105 Status
= read_file(fcb
, buffer
, 0, buflen
, retlen
, NULL
);
107 if (!NT_SUCCESS(Status
)) {
108 ERR("read_file returned %08x\n", Status
);
110 } else if (fcb
->type
== BTRFS_TYPE_DIRECTORY
) {
111 if (!fcb
->reparse_xattr
.Buffer
|| fcb
->reparse_xattr
.Length
< sizeof(ULONG
)) {
112 Status
= STATUS_NOT_A_REPARSE_POINT
;
117 *retlen
= min(buflen
, fcb
->reparse_xattr
.Length
);
118 RtlCopyMemory(buffer
, fcb
->reparse_xattr
.Buffer
, *retlen
);
122 Status
= STATUS_NOT_A_REPARSE_POINT
;
124 Status
= STATUS_NOT_A_REPARSE_POINT
;
128 ExReleaseResourceLite(fcb
->Header
.Resource
);
129 ExReleaseResourceLite(&fcb
->Vcb
->tree_lock
);
134 static NTSTATUS
set_symlink(PIRP Irp
, file_ref
* fileref
, REPARSE_DATA_BUFFER
* rdb
, ULONG buflen
, LIST_ENTRY
* rollback
) {
137 UNICODE_STRING subname
;
139 LARGE_INTEGER offset
, time
;
143 minlen
= offsetof(REPARSE_DATA_BUFFER
, SymbolicLinkReparseBuffer
.PathBuffer
) + sizeof(WCHAR
);
144 if (buflen
< minlen
) {
145 WARN("buffer was less than minimum length (%u < %u)\n", buflen
, minlen
);
146 return STATUS_INVALID_PARAMETER
;
149 subname
.Buffer
= &rdb
->SymbolicLinkReparseBuffer
.PathBuffer
[rdb
->SymbolicLinkReparseBuffer
.SubstituteNameOffset
/ sizeof(WCHAR
)];
150 subname
.MaximumLength
= subname
.Length
= rdb
->SymbolicLinkReparseBuffer
.SubstituteNameLength
;
152 TRACE("substitute name = %.*S\n", subname
.Length
/ sizeof(WCHAR
), subname
.Buffer
);
154 fileref
->fcb
->type
= BTRFS_TYPE_SYMLINK
;
156 fileref
->fcb
->inode_item
.st_mode
|= __S_IFLNK
;
158 Status
= truncate_file(fileref
->fcb
, 0, rollback
);
159 if (!NT_SUCCESS(Status
)) {
160 ERR("truncate_file returned %08x\n", Status
);
164 Status
= RtlUnicodeToUTF8N(NULL
, 0, (PULONG
)&target
.Length
, subname
.Buffer
, subname
.Length
);
165 if (!NT_SUCCESS(Status
)) {
166 ERR("RtlUnicodeToUTF8N 1 failed with error %08x\n", Status
);
170 target
.MaximumLength
= target
.Length
;
171 target
.Buffer
= ExAllocatePoolWithTag(PagedPool
, target
.MaximumLength
, ALLOC_TAG
);
172 if (!target
.Buffer
) {
173 ERR("out of memory\n");
174 return STATUS_INSUFFICIENT_RESOURCES
;
177 Status
= RtlUnicodeToUTF8N(target
.Buffer
, target
.Length
, (PULONG
)&target
.Length
, subname
.Buffer
, subname
.Length
);
178 if (!NT_SUCCESS(Status
)) {
179 ERR("RtlUnicodeToUTF8N 2 failed with error %08x\n", Status
);
180 ExFreePool(target
.Buffer
);
184 for (i
= 0; i
< target
.Length
; i
++) {
185 if (target
.Buffer
[i
] == '\\')
186 target
.Buffer
[i
] = '/';
190 Status
= write_file2(fileref
->fcb
->Vcb
, Irp
, offset
, target
.Buffer
, (ULONG
*)&target
.Length
, FALSE
, TRUE
,
191 TRUE
, FALSE
, rollback
);
192 ExFreePool(target
.Buffer
);
194 KeQuerySystemTime(&time
);
195 win_time_to_unix(time
, &now
);
197 fileref
->fcb
->inode_item
.transid
= fileref
->fcb
->Vcb
->superblock
.generation
;
198 fileref
->fcb
->inode_item
.sequence
++;
199 fileref
->fcb
->inode_item
.st_ctime
= now
;
200 fileref
->fcb
->inode_item
.st_mtime
= now
;
202 fileref
->fcb
->subvol
->root_item
.ctransid
= fileref
->fcb
->Vcb
->superblock
.generation
;
203 fileref
->fcb
->subvol
->root_item
.ctime
= now
;
205 mark_fcb_dirty(fileref
->fcb
);
206 mark_fileref_dirty(fileref
);
211 NTSTATUS
set_reparse_point(PDEVICE_OBJECT DeviceObject
, PIRP Irp
) {
212 PIO_STACK_LOCATION IrpSp
= IoGetCurrentIrpStackLocation(Irp
);
213 PFILE_OBJECT FileObject
= IrpSp
->FileObject
;
214 void* buffer
= Irp
->AssociatedIrp
.SystemBuffer
;
215 REPARSE_DATA_BUFFER
* rdb
= buffer
;
216 DWORD buflen
= IrpSp
->Parameters
.DeviceIoControl
.InputBufferLength
;
217 NTSTATUS Status
= STATUS_SUCCESS
;
224 // FIXME - send notification if this succeeds? The attributes will have changed.
225 // FIXME - check permissions
227 TRACE("(%p, %p)\n", DeviceObject
, Irp
);
229 InitializeListHead(&rollback
);
232 ERR("FileObject was NULL\n");
233 return STATUS_INVALID_PARAMETER
;
236 fcb
= FileObject
->FsContext
;
237 ccb
= FileObject
->FsContext2
;
240 ERR("ccb was NULL\n");
241 return STATUS_INVALID_PARAMETER
;
244 fileref
= ccb
->fileref
;
247 ERR("fileref was NULL\n");
248 return STATUS_INVALID_PARAMETER
;
251 TRACE("%S\n", file_desc(FileObject
));
253 ExAcquireResourceSharedLite(&fcb
->Vcb
->tree_lock
, TRUE
);
254 ExAcquireResourceExclusiveLite(fcb
->Header
.Resource
, TRUE
);
256 if (fcb
->type
== BTRFS_TYPE_SYMLINK
) {
257 WARN("tried to set a reparse point on an existing symlink\n");
258 Status
= STATUS_INVALID_PARAMETER
;
262 // FIXME - fail if we already have the attribute FILE_ATTRIBUTE_REPARSE_POINT
264 // FIXME - die if not file or directory
265 // FIXME - die if ADS
267 if (buflen
< sizeof(ULONG
)) {
268 WARN("buffer was not long enough to hold tag\n");
269 Status
= STATUS_INVALID_PARAMETER
;
273 RtlCopyMemory(&tag
, buffer
, sizeof(ULONG
));
275 if (fcb
->type
== BTRFS_TYPE_FILE
&& tag
== IO_REPARSE_TAG_SYMLINK
&& rdb
->SymbolicLinkReparseBuffer
.Flags
& SYMLINK_FLAG_RELATIVE
) {
276 Status
= set_symlink(Irp
, fileref
, rdb
, buflen
, &rollback
);
277 fcb
->atts
|= FILE_ATTRIBUTE_REPARSE_POINT
;
279 LARGE_INTEGER offset
, time
;
282 if (fcb
->type
== BTRFS_TYPE_DIRECTORY
) { // for directories, store as xattr
285 buf
.Buffer
= ExAllocatePoolWithTag(PagedPool
, buflen
, ALLOC_TAG
);
287 ERR("out of memory\n");
288 Status
= STATUS_INSUFFICIENT_RESOURCES
;
291 buf
.Length
= buf
.MaximumLength
= buflen
;
293 if (fcb
->reparse_xattr
.Buffer
)
294 ExFreePool(fcb
->reparse_xattr
.Buffer
);
296 fcb
->reparse_xattr
= buf
;
297 RtlCopyMemory(fcb
->reparse_xattr
.Buffer
, buffer
, buflen
);
299 fcb
->reparse_xattr_changed
= TRUE
;
301 Status
= STATUS_SUCCESS
;
302 } else { // otherwise, store as file data
303 Status
= truncate_file(fcb
, 0, &rollback
);
304 if (!NT_SUCCESS(Status
)) {
305 ERR("truncate_file returned %08x\n", Status
);
311 Status
= write_file2(fcb
->Vcb
, Irp
, offset
, buffer
, &buflen
, FALSE
, TRUE
, TRUE
, FALSE
, &rollback
);
312 if (!NT_SUCCESS(Status
)) {
313 ERR("write_file2 returned %08x\n", Status
);
318 KeQuerySystemTime(&time
);
319 win_time_to_unix(time
, &now
);
321 fcb
->inode_item
.transid
= fcb
->Vcb
->superblock
.generation
;
322 fcb
->inode_item
.sequence
++;
323 fcb
->inode_item
.st_ctime
= now
;
324 fcb
->inode_item
.st_mtime
= now
;
325 fcb
->atts
|= FILE_ATTRIBUTE_REPARSE_POINT
;
326 fcb
->atts_changed
= TRUE
;
328 fcb
->subvol
->root_item
.ctransid
= fcb
->Vcb
->superblock
.generation
;
329 fcb
->subvol
->root_item
.ctime
= now
;
334 send_notification_fcb(fileref
, FILE_NOTIFY_CHANGE_LAST_WRITE
| FILE_NOTIFY_CHANGE_ATTRIBUTES
, FILE_ACTION_MODIFIED
);
337 if (NT_SUCCESS(Status
))
338 clear_rollback(&rollback
);
340 do_rollback(fcb
->Vcb
, &rollback
);
342 ExReleaseResourceLite(fcb
->Header
.Resource
);
343 ExReleaseResourceLite(&fcb
->Vcb
->tree_lock
);
348 NTSTATUS
delete_reparse_point(PDEVICE_OBJECT DeviceObject
, PIRP Irp
) {
349 PIO_STACK_LOCATION IrpSp
= IoGetCurrentIrpStackLocation(Irp
);
350 PFILE_OBJECT FileObject
= IrpSp
->FileObject
;
351 REPARSE_DATA_BUFFER
* rdb
= Irp
->AssociatedIrp
.SystemBuffer
;
352 DWORD buflen
= IrpSp
->Parameters
.DeviceIoControl
.InputBufferLength
;
359 // FIXME - check permissions
361 TRACE("(%p, %p)\n", DeviceObject
, Irp
);
363 InitializeListHead(&rollback
);
366 ERR("FileObject was NULL\n");
367 return STATUS_INVALID_PARAMETER
;
370 fcb
= FileObject
->FsContext
;
371 ccb
= FileObject
->FsContext2
;
372 fileref
= ccb
? ccb
->fileref
: NULL
;
374 ExAcquireResourceSharedLite(&fcb
->Vcb
->tree_lock
, TRUE
);
375 ExAcquireResourceExclusiveLite(fcb
->Header
.Resource
, TRUE
);
377 TRACE("%S\n", file_desc(FileObject
));
380 ERR("fileref was NULL\n");
381 Status
= STATUS_INVALID_PARAMETER
;
385 if (buflen
< offsetof(REPARSE_DATA_BUFFER
, GenericReparseBuffer
.DataBuffer
)) {
386 ERR("buffer was too short\n");
387 Status
= STATUS_INVALID_PARAMETER
;
391 if (rdb
->ReparseDataLength
> 0) {
392 WARN("rdb->ReparseDataLength was not zero\n");
393 Status
= STATUS_INVALID_PARAMETER
;
398 WARN("tried to delete reparse point on ADS\n");
399 Status
= STATUS_INVALID_PARAMETER
;
403 if (fcb
->type
== BTRFS_TYPE_SYMLINK
) {
407 if (rdb
->ReparseTag
!= IO_REPARSE_TAG_SYMLINK
) {
408 WARN("reparse tag was not IO_REPARSE_TAG_SYMLINK\n");
409 Status
= STATUS_INVALID_PARAMETER
;
413 KeQuerySystemTime(&time
);
414 win_time_to_unix(time
, &now
);
416 fileref
->fcb
->type
= BTRFS_TYPE_FILE
;
417 fileref
->fcb
->inode_item
.st_mode
&= ~__S_IFLNK
;
418 fileref
->fcb
->inode_item
.st_mode
|= __S_IFREG
;
419 fileref
->fcb
->inode_item
.transid
= fileref
->fcb
->Vcb
->superblock
.generation
;
420 fileref
->fcb
->inode_item
.sequence
++;
421 fileref
->fcb
->inode_item
.st_ctime
= now
;
422 fileref
->fcb
->inode_item
.st_mtime
= now
;
423 fileref
->fcb
->atts
&= ~FILE_ATTRIBUTE_REPARSE_POINT
;
425 mark_fileref_dirty(fileref
);
426 mark_fcb_dirty(fileref
->fcb
);
428 fileref
->fcb
->subvol
->root_item
.ctransid
= fcb
->Vcb
->superblock
.generation
;
429 fileref
->fcb
->subvol
->root_item
.ctime
= now
;
430 } else if (fcb
->type
== BTRFS_TYPE_FILE
) {
434 // FIXME - do we need to check that the reparse tags match?
436 Status
= truncate_file(fcb
, 0, &rollback
);
437 if (!NT_SUCCESS(Status
)) {
438 ERR("truncate_file returned %08x\n", Status
);
442 fcb
->atts
&= ~FILE_ATTRIBUTE_REPARSE_POINT
;
443 fcb
->atts_changed
= TRUE
;
445 KeQuerySystemTime(&time
);
446 win_time_to_unix(time
, &now
);
448 fcb
->inode_item
.transid
= fcb
->Vcb
->superblock
.generation
;
449 fcb
->inode_item
.sequence
++;
450 fcb
->inode_item
.st_ctime
= now
;
451 fcb
->inode_item
.st_mtime
= now
;
455 fcb
->subvol
->root_item
.ctransid
= fcb
->Vcb
->superblock
.generation
;
456 fcb
->subvol
->root_item
.ctime
= now
;
457 } else if (fcb
->type
== BTRFS_TYPE_DIRECTORY
) {
461 // FIXME - do we need to check that the reparse tags match?
463 fcb
->atts
&= ~FILE_ATTRIBUTE_REPARSE_POINT
;
464 fcb
->atts_changed
= TRUE
;
466 if (fcb
->reparse_xattr
.Buffer
) {
467 ExFreePool(fcb
->reparse_xattr
.Buffer
);
468 fcb
->reparse_xattr
.Buffer
= NULL
;
471 fcb
->reparse_xattr_changed
= TRUE
;
473 KeQuerySystemTime(&time
);
474 win_time_to_unix(time
, &now
);
476 fcb
->inode_item
.transid
= fcb
->Vcb
->superblock
.generation
;
477 fcb
->inode_item
.sequence
++;
478 fcb
->inode_item
.st_ctime
= now
;
479 fcb
->inode_item
.st_mtime
= now
;
483 fcb
->subvol
->root_item
.ctransid
= fcb
->Vcb
->superblock
.generation
;
484 fcb
->subvol
->root_item
.ctime
= now
;
486 ERR("unsupported file type %u\n", fcb
->type
);
487 Status
= STATUS_INVALID_PARAMETER
;
491 Status
= STATUS_SUCCESS
;
493 send_notification_fcb(fileref
, FILE_NOTIFY_CHANGE_LAST_WRITE
| FILE_NOTIFY_CHANGE_ATTRIBUTES
, FILE_ACTION_MODIFIED
);
496 if (NT_SUCCESS(Status
))
497 clear_rollback(&rollback
);
499 do_rollback(fcb
->Vcb
, &rollback
);
501 ExReleaseResourceLite(fcb
->Header
.Resource
);
502 ExReleaseResourceLite(&fcb
->Vcb
->tree_lock
);