/* 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 . */ #include "btrfs_drv.h" extern tFsRtlValidateReparsePointBuffer fFsRtlValidateReparsePointBuffer; NTSTATUS get_reparse_point(PDEVICE_OBJECT DeviceObject, 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, %p, %lx, %p)\n", DeviceObject, 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, bool write, LIST_ENTRY* rollback) { NTSTATUS Status; ULONG minlen; ULONG tlength; UNICODE_STRING subname; ANSI_STRING target; LARGE_INTEGER offset, time; BTRFS_TIME now; USHORT i; if (write) { 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); } fcb->type = BTRFS_TYPE_SYMLINK; 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; if (write) { Status = truncate_file(fcb, 0, Irp, rollback); if (!NT_SUCCESS(Status)) { ERR("truncate_file returned %08lx\n", Status); return Status; } Status = utf16_to_utf8(NULL, 0, (PULONG)&target.Length, 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; target.Buffer = ExAllocatePoolWithTag(PagedPool, target.MaximumLength, ALLOC_TAG); if (!target.Buffer) { ERR("out of memory\n"); return STATUS_INSUFFICIENT_RESOURCES; } Status = utf16_to_utf8(target.Buffer, target.Length, (PULONG)&target.Length, 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 (i = 0; i < target.MaximumLength; i++) { if (target.Buffer[i] == '\\') target.Buffer[i] = '/'; } offset.QuadPart = 0; tlength = target.Length; Status = write_file2(fcb->Vcb, Irp, offset, target.Buffer, &tlength, false, true, true, false, false, rollback); ExFreePool(target.Buffer); } else Status = STATUS_SUCCESS; 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 (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; } RtlCopyMemory(&tag, rdb, sizeof(ULONG)); 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, tag == IO_REPARSE_TAG_SYMLINK, 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(PDEVICE_OBJECT DeviceObject, 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, %p)\n", DeviceObject, 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(PDEVICE_OBJECT DeviceObject, 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, %p)\n", DeviceObject, 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; }