mirror of
https://github.com/reactos/reactos.git
synced 2024-12-28 01:55:19 +00:00
c982533ea9
v1.7.6 (2021-01-14): - Fixed race condition when booting with Quibble - No longer need to restart Windows after initial installation - Forced maximum file name to 255 UTF-8 characters, to match Linux driver - Fixed issue where directories could be created with trailing backslash - Fixed potential deadlock when Windows calls NtCreateSection during flush - Miscellaneous bug fixes
672 lines
22 KiB
C
672 lines
22 KiB
C
/* Copyright (c) Mark Harmstone 2016-17
|
|
*
|
|
* This file is part of WinBtrfs.
|
|
*
|
|
* WinBtrfs is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public Licence as published by
|
|
* the Free Software Foundation, either version 3 of the Licence, or
|
|
* (at your option) any later version.
|
|
*
|
|
* WinBtrfs is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public Licence for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public Licence
|
|
* along with WinBtrfs. If not, see <http://www.gnu.org/licenses/>. */
|
|
|
|
#include "btrfs_drv.h"
|
|
|
|
extern tFsRtlValidateReparsePointBuffer fFsRtlValidateReparsePointBuffer;
|
|
|
|
typedef struct {
|
|
uint32_t unknown;
|
|
char name[1];
|
|
} REPARSE_DATA_BUFFER_LX_SYMLINK;
|
|
|
|
NTSTATUS get_reparse_point(PFILE_OBJECT FileObject, void* buffer, DWORD buflen, ULONG_PTR* retlen) {
|
|
USHORT subnamelen, printnamelen, i;
|
|
ULONG stringlen;
|
|
DWORD reqlen;
|
|
REPARSE_DATA_BUFFER* rdb = buffer;
|
|
fcb* fcb = FileObject->FsContext;
|
|
ccb* ccb = FileObject->FsContext2;
|
|
NTSTATUS Status;
|
|
|
|
TRACE("(%p, %p, %lx, %p)\n", FileObject, buffer, buflen, retlen);
|
|
|
|
if (!ccb)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true);
|
|
ExAcquireResourceSharedLite(fcb->Header.Resource, true);
|
|
|
|
if (fcb->type == BTRFS_TYPE_SYMLINK) {
|
|
if (ccb->lxss) {
|
|
reqlen = offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + sizeof(uint32_t);
|
|
|
|
if (buflen < reqlen) {
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
goto end;
|
|
}
|
|
|
|
rdb->ReparseTag = IO_REPARSE_TAG_LX_SYMLINK;
|
|
rdb->ReparseDataLength = offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + sizeof(uint32_t);
|
|
rdb->Reserved = 0;
|
|
|
|
*((uint32_t*)rdb->GenericReparseBuffer.DataBuffer) = 1;
|
|
|
|
*retlen = reqlen;
|
|
} else {
|
|
char* data;
|
|
|
|
if (fcb->inode_item.st_size == 0 || fcb->inode_item.st_size > 0xffff) {
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
data = ExAllocatePoolWithTag(PagedPool, (ULONG)fcb->inode_item.st_size, ALLOC_TAG);
|
|
if (!data) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
TRACE("data = %p, size = %I64x\n", data, fcb->inode_item.st_size);
|
|
Status = read_file(fcb, (uint8_t*)data, 0, fcb->inode_item.st_size, NULL, NULL);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("read_file returned %08lx\n", Status);
|
|
ExFreePool(data);
|
|
goto end;
|
|
}
|
|
|
|
Status = utf8_to_utf16(NULL, 0, &stringlen, data, (ULONG)fcb->inode_item.st_size);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("utf8_to_utf16 1 returned %08lx\n", Status);
|
|
ExFreePool(data);
|
|
goto end;
|
|
}
|
|
|
|
subnamelen = (uint16_t)stringlen;
|
|
printnamelen = (uint16_t)stringlen;
|
|
|
|
reqlen = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) + subnamelen + printnamelen;
|
|
|
|
if (buflen >= offsetof(REPARSE_DATA_BUFFER, ReparseDataLength))
|
|
rdb->ReparseTag = IO_REPARSE_TAG_SYMLINK;
|
|
|
|
if (buflen >= offsetof(REPARSE_DATA_BUFFER, Reserved))
|
|
rdb->ReparseDataLength = (USHORT)(reqlen - offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer));
|
|
|
|
if (buflen >= offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.SubstituteNameOffset))
|
|
rdb->Reserved = 0;
|
|
|
|
if (buflen < reqlen) {
|
|
ExFreePool(data);
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
*retlen = min(buflen, offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.SubstituteNameOffset));
|
|
goto end;
|
|
}
|
|
|
|
rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0;
|
|
rdb->SymbolicLinkReparseBuffer.SubstituteNameLength = subnamelen;
|
|
rdb->SymbolicLinkReparseBuffer.PrintNameOffset = subnamelen;
|
|
rdb->SymbolicLinkReparseBuffer.PrintNameLength = printnamelen;
|
|
rdb->SymbolicLinkReparseBuffer.Flags = SYMLINK_FLAG_RELATIVE;
|
|
|
|
Status = utf8_to_utf16(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)],
|
|
stringlen, &stringlen, data, (ULONG)fcb->inode_item.st_size);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("utf8_to_utf16 2 returned %08lx\n", Status);
|
|
ExFreePool(data);
|
|
goto end;
|
|
}
|
|
|
|
for (i = 0; i < stringlen / sizeof(WCHAR); i++) {
|
|
if (rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] == '/')
|
|
rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] = '\\';
|
|
}
|
|
|
|
RtlCopyMemory(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)],
|
|
&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)],
|
|
rdb->SymbolicLinkReparseBuffer.SubstituteNameLength);
|
|
|
|
*retlen = reqlen;
|
|
|
|
ExFreePool(data);
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
} else if (fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT) {
|
|
if (fcb->type == BTRFS_TYPE_FILE) {
|
|
ULONG len;
|
|
|
|
Status = read_file(fcb, buffer, 0, buflen, &len, NULL);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("read_file returned %08lx\n", Status);
|
|
}
|
|
|
|
*retlen = len;
|
|
} else if (fcb->type == BTRFS_TYPE_DIRECTORY) {
|
|
if (!fcb->reparse_xattr.Buffer || fcb->reparse_xattr.Length < sizeof(ULONG)) {
|
|
Status = STATUS_NOT_A_REPARSE_POINT;
|
|
goto end;
|
|
}
|
|
|
|
if (buflen > 0) {
|
|
*retlen = min(buflen, fcb->reparse_xattr.Length);
|
|
RtlCopyMemory(buffer, fcb->reparse_xattr.Buffer, *retlen);
|
|
} else
|
|
*retlen = 0;
|
|
|
|
Status = *retlen == fcb->reparse_xattr.Length ? STATUS_SUCCESS : STATUS_BUFFER_OVERFLOW;
|
|
} else
|
|
Status = STATUS_NOT_A_REPARSE_POINT;
|
|
} else {
|
|
Status = STATUS_NOT_A_REPARSE_POINT;
|
|
}
|
|
|
|
end:
|
|
ExReleaseResourceLite(fcb->Header.Resource);
|
|
ExReleaseResourceLite(&fcb->Vcb->tree_lock);
|
|
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS set_symlink(PIRP Irp, file_ref* fileref, fcb* fcb, ccb* ccb, REPARSE_DATA_BUFFER* rdb, ULONG buflen, LIST_ENTRY* rollback) {
|
|
NTSTATUS Status;
|
|
ULONG tlength;
|
|
ANSI_STRING target;
|
|
bool target_alloc = false;
|
|
LARGE_INTEGER offset, time;
|
|
BTRFS_TIME now;
|
|
|
|
if (rdb->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
|
|
UNICODE_STRING subname;
|
|
ULONG minlen, len;
|
|
|
|
minlen = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) + sizeof(WCHAR);
|
|
if (buflen < minlen) {
|
|
WARN("buffer was less than minimum length (%lu < %lu)\n", buflen, minlen);
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (rdb->SymbolicLinkReparseBuffer.SubstituteNameLength < sizeof(WCHAR)) {
|
|
WARN("rdb->SymbolicLinkReparseBuffer.SubstituteNameLength was too short\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
subname.Buffer = &rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)];
|
|
subname.MaximumLength = subname.Length = rdb->SymbolicLinkReparseBuffer.SubstituteNameLength;
|
|
|
|
TRACE("substitute name = %.*S\n", (int)(subname.Length / sizeof(WCHAR)), subname.Buffer);
|
|
|
|
Status = utf16_to_utf8(NULL, 0, &len, subname.Buffer, subname.Length);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("utf16_to_utf8 1 failed with error %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
target.MaximumLength = target.Length = (USHORT)len;
|
|
target.Buffer = ExAllocatePoolWithTag(PagedPool, target.MaximumLength, ALLOC_TAG);
|
|
if (!target.Buffer) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
target_alloc = true;
|
|
|
|
Status = utf16_to_utf8(target.Buffer, target.Length, &len, subname.Buffer, subname.Length);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("utf16_to_utf8 2 failed with error %08lx\n", Status);
|
|
ExFreePool(target.Buffer);
|
|
return Status;
|
|
}
|
|
|
|
for (USHORT i = 0; i < target.Length; i++) {
|
|
if (target.Buffer[i] == '\\')
|
|
target.Buffer[i] = '/';
|
|
}
|
|
} else if (rdb->ReparseTag == IO_REPARSE_TAG_LX_SYMLINK) {
|
|
REPARSE_DATA_BUFFER_LX_SYMLINK* buf;
|
|
|
|
if (buflen < offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + rdb->ReparseDataLength) {
|
|
WARN("buffer was less than expected length (%lu < %lu)\n", buflen,
|
|
(unsigned long)(offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + rdb->ReparseDataLength));
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
buf = (REPARSE_DATA_BUFFER_LX_SYMLINK*)rdb->GenericReparseBuffer.DataBuffer;
|
|
|
|
if (buflen < offsetof(REPARSE_DATA_BUFFER_LX_SYMLINK, name)) {
|
|
WARN("buffer was less than minimum length (%u < %lu)\n", rdb->ReparseDataLength,
|
|
(unsigned long)(offsetof(REPARSE_DATA_BUFFER_LX_SYMLINK, name)));
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
target.Buffer = buf->name;
|
|
target.Length = target.MaximumLength = rdb->ReparseDataLength - offsetof(REPARSE_DATA_BUFFER_LX_SYMLINK, name);
|
|
} else {
|
|
ERR("unexpected reparse tag %08lx\n", rdb->ReparseTag);
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
fcb->type = BTRFS_TYPE_SYMLINK;
|
|
fcb->inode_item.st_mode &= ~__S_IFMT;
|
|
fcb->inode_item.st_mode |= __S_IFLNK;
|
|
fcb->inode_item.generation = fcb->Vcb->superblock.generation; // so we don't confuse btrfs send on Linux
|
|
|
|
if (fileref && fileref->dc)
|
|
fileref->dc->type = fcb->type;
|
|
|
|
Status = truncate_file(fcb, 0, Irp, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("truncate_file returned %08lx\n", Status);
|
|
|
|
if (target_alloc)
|
|
ExFreePool(target.Buffer);
|
|
|
|
return Status;
|
|
}
|
|
|
|
offset.QuadPart = 0;
|
|
tlength = target.Length;
|
|
Status = write_file2(fcb->Vcb, Irp, offset, target.Buffer, &tlength, false, true,
|
|
true, false, false, rollback);
|
|
|
|
if (target_alloc)
|
|
ExFreePool(target.Buffer);
|
|
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &now);
|
|
|
|
fcb->inode_item.transid = fcb->Vcb->superblock.generation;
|
|
fcb->inode_item.sequence++;
|
|
|
|
if (!ccb || !ccb->user_set_change_time)
|
|
fcb->inode_item.st_ctime = now;
|
|
|
|
if (!ccb || !ccb->user_set_write_time)
|
|
fcb->inode_item.st_mtime = now;
|
|
|
|
fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
|
|
fcb->subvol->root_item.ctime = now;
|
|
|
|
fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(fcb);
|
|
|
|
if (fileref)
|
|
mark_fileref_dirty(fileref);
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS set_reparse_point2(fcb* fcb, REPARSE_DATA_BUFFER* rdb, ULONG buflen, ccb* ccb, file_ref* fileref, PIRP Irp, LIST_ENTRY* rollback) {
|
|
NTSTATUS Status;
|
|
ULONG tag;
|
|
|
|
if (fcb->type == BTRFS_TYPE_SYMLINK) {
|
|
WARN("tried to set a reparse point on an existing symlink\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
// FIXME - fail if we already have the attribute FILE_ATTRIBUTE_REPARSE_POINT
|
|
|
|
// FIXME - die if not file or directory
|
|
|
|
if (fcb->type == BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0) {
|
|
TRACE("directory not empty\n");
|
|
return STATUS_DIRECTORY_NOT_EMPTY;
|
|
}
|
|
|
|
if (buflen < sizeof(ULONG)) {
|
|
WARN("buffer was not long enough to hold tag\n");
|
|
return STATUS_INVALID_BUFFER_SIZE;
|
|
}
|
|
|
|
Status = fFsRtlValidateReparsePointBuffer(buflen, rdb);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("FsRtlValidateReparsePointBuffer returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
tag = *(ULONG*)rdb;
|
|
|
|
if (tag == IO_REPARSE_TAG_MOUNT_POINT && fcb->type != BTRFS_TYPE_DIRECTORY)
|
|
return STATUS_NOT_A_DIRECTORY;
|
|
|
|
if (fcb->type == BTRFS_TYPE_FILE &&
|
|
((tag == IO_REPARSE_TAG_SYMLINK && rdb->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE) || tag == IO_REPARSE_TAG_LX_SYMLINK)) {
|
|
Status = set_symlink(Irp, fileref, fcb, ccb, rdb, buflen, rollback);
|
|
fcb->atts |= FILE_ATTRIBUTE_REPARSE_POINT;
|
|
} else {
|
|
LARGE_INTEGER offset, time;
|
|
BTRFS_TIME now;
|
|
|
|
if (fcb->type == BTRFS_TYPE_DIRECTORY || fcb->type == BTRFS_TYPE_CHARDEV || fcb->type == BTRFS_TYPE_BLOCKDEV) { // store as xattr
|
|
ANSI_STRING buf;
|
|
|
|
buf.Buffer = ExAllocatePoolWithTag(PagedPool, buflen, ALLOC_TAG);
|
|
if (!buf.Buffer) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
buf.Length = buf.MaximumLength = (uint16_t)buflen;
|
|
|
|
if (fcb->reparse_xattr.Buffer)
|
|
ExFreePool(fcb->reparse_xattr.Buffer);
|
|
|
|
fcb->reparse_xattr = buf;
|
|
RtlCopyMemory(buf.Buffer, rdb, buflen);
|
|
|
|
fcb->reparse_xattr_changed = true;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
} else { // otherwise, store as file data
|
|
Status = truncate_file(fcb, 0, Irp, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("truncate_file returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
offset.QuadPart = 0;
|
|
|
|
Status = write_file2(fcb->Vcb, Irp, offset, rdb, &buflen, false, true, true, false, false, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("write_file2 returned %08lx\n", Status);
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &now);
|
|
|
|
fcb->inode_item.transid = fcb->Vcb->superblock.generation;
|
|
fcb->inode_item.sequence++;
|
|
|
|
if (!ccb || !ccb->user_set_change_time)
|
|
fcb->inode_item.st_ctime = now;
|
|
|
|
if (!ccb || !ccb->user_set_write_time)
|
|
fcb->inode_item.st_mtime = now;
|
|
|
|
fcb->atts |= FILE_ATTRIBUTE_REPARSE_POINT;
|
|
fcb->atts_changed = true;
|
|
|
|
fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
|
|
fcb->subvol->root_item.ctime = now;
|
|
|
|
fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(fcb);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS set_reparse_point(PIRP Irp) {
|
|
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
PFILE_OBJECT FileObject = IrpSp->FileObject;
|
|
void* buffer = Irp->AssociatedIrp.SystemBuffer;
|
|
REPARSE_DATA_BUFFER* rdb = buffer;
|
|
DWORD buflen = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
fcb* fcb;
|
|
ccb* ccb;
|
|
file_ref* fileref;
|
|
LIST_ENTRY rollback;
|
|
|
|
TRACE("(%p)\n", Irp);
|
|
|
|
InitializeListHead(&rollback);
|
|
|
|
if (!FileObject) {
|
|
ERR("FileObject was NULL\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
// IFSTest insists on this, for some reason...
|
|
if (Irp->UserBuffer)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
fcb = FileObject->FsContext;
|
|
ccb = FileObject->FsContext2;
|
|
|
|
if (!ccb) {
|
|
ERR("ccb was NULL\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (Irp->RequestorMode == UserMode && !(ccb->access & (FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA))) {
|
|
WARN("insufficient privileges\n");
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
fileref = ccb->fileref;
|
|
|
|
if (!fileref) {
|
|
ERR("fileref was NULL\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (fcb->ads) {
|
|
fileref = fileref->parent;
|
|
fcb = fileref->fcb;
|
|
}
|
|
|
|
ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true);
|
|
ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);
|
|
|
|
Status = set_reparse_point2(fcb, rdb, buflen, ccb, fileref, Irp, &rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("set_reparse_point2 returned %08lx\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
queue_notification_fcb(fileref, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_ACTION_MODIFIED, NULL);
|
|
|
|
end:
|
|
if (NT_SUCCESS(Status))
|
|
clear_rollback(&rollback);
|
|
else
|
|
do_rollback(fcb->Vcb, &rollback);
|
|
|
|
ExReleaseResourceLite(fcb->Header.Resource);
|
|
ExReleaseResourceLite(&fcb->Vcb->tree_lock);
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS delete_reparse_point(PIRP Irp) {
|
|
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
PFILE_OBJECT FileObject = IrpSp->FileObject;
|
|
REPARSE_DATA_BUFFER* rdb = Irp->AssociatedIrp.SystemBuffer;
|
|
DWORD buflen = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
|
|
NTSTATUS Status;
|
|
fcb* fcb;
|
|
ccb* ccb;
|
|
file_ref* fileref;
|
|
LIST_ENTRY rollback;
|
|
|
|
TRACE("(%p)\n", Irp);
|
|
|
|
InitializeListHead(&rollback);
|
|
|
|
if (!FileObject) {
|
|
ERR("FileObject was NULL\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
fcb = FileObject->FsContext;
|
|
|
|
if (!fcb) {
|
|
ERR("fcb was NULL\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
ccb = FileObject->FsContext2;
|
|
|
|
if (!ccb) {
|
|
ERR("ccb was NULL\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_ATTRIBUTES)) {
|
|
WARN("insufficient privileges\n");
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
fileref = ccb->fileref;
|
|
|
|
if (!fileref) {
|
|
ERR("fileref was NULL\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true);
|
|
ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);
|
|
|
|
if (buflen < offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer)) {
|
|
ERR("buffer was too short\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
if (rdb->ReparseDataLength > 0) {
|
|
WARN("rdb->ReparseDataLength was not zero\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
if (fcb->ads) {
|
|
WARN("tried to delete reparse point on ADS\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
if (fcb->type == BTRFS_TYPE_SYMLINK) {
|
|
LARGE_INTEGER time;
|
|
BTRFS_TIME now;
|
|
|
|
if (rdb->ReparseTag != IO_REPARSE_TAG_SYMLINK) {
|
|
WARN("reparse tag was not IO_REPARSE_TAG_SYMLINK\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &now);
|
|
|
|
fileref->fcb->type = BTRFS_TYPE_FILE;
|
|
fileref->fcb->inode_item.st_mode &= ~__S_IFLNK;
|
|
fileref->fcb->inode_item.st_mode |= __S_IFREG;
|
|
fileref->fcb->inode_item.generation = fileref->fcb->Vcb->superblock.generation; // so we don't confuse btrfs send on Linux
|
|
fileref->fcb->inode_item.transid = fileref->fcb->Vcb->superblock.generation;
|
|
fileref->fcb->inode_item.sequence++;
|
|
|
|
if (!ccb->user_set_change_time)
|
|
fileref->fcb->inode_item.st_ctime = now;
|
|
|
|
if (!ccb->user_set_write_time)
|
|
fileref->fcb->inode_item.st_mtime = now;
|
|
|
|
fileref->fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;
|
|
|
|
if (fileref->dc)
|
|
fileref->dc->type = fileref->fcb->type;
|
|
|
|
mark_fileref_dirty(fileref);
|
|
|
|
fileref->fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(fileref->fcb);
|
|
|
|
fileref->fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
|
|
fileref->fcb->subvol->root_item.ctime = now;
|
|
} else if (fcb->type == BTRFS_TYPE_FILE) {
|
|
LARGE_INTEGER time;
|
|
BTRFS_TIME now;
|
|
|
|
// FIXME - do we need to check that the reparse tags match?
|
|
|
|
Status = truncate_file(fcb, 0, Irp, &rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("truncate_file returned %08lx\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;
|
|
fcb->atts_changed = true;
|
|
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &now);
|
|
|
|
fcb->inode_item.transid = fcb->Vcb->superblock.generation;
|
|
fcb->inode_item.sequence++;
|
|
|
|
if (!ccb->user_set_change_time)
|
|
fcb->inode_item.st_ctime = now;
|
|
|
|
if (!ccb->user_set_write_time)
|
|
fcb->inode_item.st_mtime = now;
|
|
|
|
fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(fcb);
|
|
|
|
fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
|
|
fcb->subvol->root_item.ctime = now;
|
|
} else if (fcb->type == BTRFS_TYPE_DIRECTORY) {
|
|
LARGE_INTEGER time;
|
|
BTRFS_TIME now;
|
|
|
|
// FIXME - do we need to check that the reparse tags match?
|
|
|
|
fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;
|
|
fcb->atts_changed = true;
|
|
|
|
if (fcb->reparse_xattr.Buffer) {
|
|
ExFreePool(fcb->reparse_xattr.Buffer);
|
|
fcb->reparse_xattr.Buffer = NULL;
|
|
}
|
|
|
|
fcb->reparse_xattr_changed = true;
|
|
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &now);
|
|
|
|
fcb->inode_item.transid = fcb->Vcb->superblock.generation;
|
|
fcb->inode_item.sequence++;
|
|
|
|
if (!ccb->user_set_change_time)
|
|
fcb->inode_item.st_ctime = now;
|
|
|
|
if (!ccb->user_set_write_time)
|
|
fcb->inode_item.st_mtime = now;
|
|
|
|
fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(fcb);
|
|
|
|
fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;
|
|
fcb->subvol->root_item.ctime = now;
|
|
} else {
|
|
ERR("unsupported file type %u\n", fcb->type);
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
queue_notification_fcb(fileref, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_ACTION_MODIFIED, NULL);
|
|
|
|
end:
|
|
if (NT_SUCCESS(Status))
|
|
clear_rollback(&rollback);
|
|
else
|
|
do_rollback(fcb->Vcb, &rollback);
|
|
|
|
ExReleaseResourceLite(fcb->Header.Resource);
|
|
ExReleaseResourceLite(&fcb->Vcb->tree_lock);
|
|
|
|
return Status;
|
|
}
|