mirror of
https://github.com/reactos/reactos.git
synced 2024-11-20 14:30:57 +00:00
62e630de4c
CORE-16494
5952 lines
198 KiB
C
5952 lines
198 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"
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_WIN10)
|
|
// not currently in mingw - introduced with Windows 10
|
|
#ifndef _MSC_VER
|
|
#define FileIdInformation (enum _FILE_INFORMATION_CLASS)59
|
|
#define FileHardLinkFullIdInformation (enum _FILE_INFORMATION_CLASS)62
|
|
#define FileDispositionInformationEx (enum _FILE_INFORMATION_CLASS)64
|
|
#define FileRenameInformationEx (enum _FILE_INFORMATION_CLASS)65
|
|
#define FileStatInformation (enum _FILE_INFORMATION_CLASS)68
|
|
#define FileStatLxInformation (enum _FILE_INFORMATION_CLASS)70
|
|
#define FileCaseSensitiveInformation (enum _FILE_INFORMATION_CLASS)71
|
|
#define FileLinkInformationEx (enum _FILE_INFORMATION_CLASS)72
|
|
|
|
typedef struct _FILE_ID_INFORMATION {
|
|
ULONGLONG VolumeSerialNumber;
|
|
FILE_ID_128 FileId;
|
|
} FILE_ID_INFORMATION, *PFILE_ID_INFORMATION;
|
|
|
|
typedef struct _FILE_STAT_INFORMATION {
|
|
LARGE_INTEGER FileId;
|
|
LARGE_INTEGER CreationTime;
|
|
LARGE_INTEGER LastAccessTime;
|
|
LARGE_INTEGER LastWriteTime;
|
|
LARGE_INTEGER ChangeTime;
|
|
LARGE_INTEGER AllocationSize;
|
|
LARGE_INTEGER EndOfFile;
|
|
ULONG FileAttributes;
|
|
ULONG ReparseTag;
|
|
ULONG NumberOfLinks;
|
|
ACCESS_MASK EffectiveAccess;
|
|
} FILE_STAT_INFORMATION, *PFILE_STAT_INFORMATION;
|
|
|
|
typedef struct _FILE_STAT_LX_INFORMATION {
|
|
LARGE_INTEGER FileId;
|
|
LARGE_INTEGER CreationTime;
|
|
LARGE_INTEGER LastAccessTime;
|
|
LARGE_INTEGER LastWriteTime;
|
|
LARGE_INTEGER ChangeTime;
|
|
LARGE_INTEGER AllocationSize;
|
|
LARGE_INTEGER EndOfFile;
|
|
ULONG FileAttributes;
|
|
ULONG ReparseTag;
|
|
ULONG NumberOfLinks;
|
|
ACCESS_MASK EffectiveAccess;
|
|
ULONG LxFlags;
|
|
ULONG LxUid;
|
|
ULONG LxGid;
|
|
ULONG LxMode;
|
|
ULONG LxDeviceIdMajor;
|
|
ULONG LxDeviceIdMinor;
|
|
} FILE_STAT_LX_INFORMATION, *PFILE_STAT_LX_INFORMATION;
|
|
|
|
#define LX_FILE_METADATA_HAS_UID 0x01
|
|
#define LX_FILE_METADATA_HAS_GID 0x02
|
|
#define LX_FILE_METADATA_HAS_MODE 0x04
|
|
#define LX_FILE_METADATA_HAS_DEVICE_ID 0x08
|
|
#define LX_FILE_CASE_SENSITIVE_DIR 0x10
|
|
|
|
typedef struct _FILE_RENAME_INFORMATION_EX {
|
|
union {
|
|
BOOLEAN ReplaceIfExists;
|
|
ULONG Flags;
|
|
};
|
|
HANDLE RootDirectory;
|
|
ULONG FileNameLength;
|
|
WCHAR FileName[1];
|
|
} FILE_RENAME_INFORMATION_EX, *PFILE_RENAME_INFORMATION_EX;
|
|
|
|
typedef struct _FILE_DISPOSITION_INFORMATION_EX {
|
|
ULONG Flags;
|
|
} FILE_DISPOSITION_INFORMATION_EX, *PFILE_DISPOSITION_INFORMATION_EX;
|
|
|
|
typedef struct _FILE_LINK_INFORMATION_EX {
|
|
union {
|
|
BOOLEAN ReplaceIfExists;
|
|
ULONG Flags;
|
|
};
|
|
HANDLE RootDirectory;
|
|
ULONG FileNameLength;
|
|
WCHAR FileName[1];
|
|
} FILE_LINK_INFORMATION_EX, *PFILE_LINK_INFORMATION_EX;
|
|
|
|
typedef struct _FILE_CASE_SENSITIVE_INFORMATION {
|
|
ULONG Flags;
|
|
} FILE_CASE_SENSITIVE_INFORMATION, *PFILE_CASE_SENSITIVE_INFORMATION;
|
|
|
|
typedef struct _FILE_LINK_ENTRY_FULL_ID_INFORMATION {
|
|
ULONG NextEntryOffset;
|
|
FILE_ID_128 ParentFileId;
|
|
ULONG FileNameLength;
|
|
WCHAR FileName[1];
|
|
} FILE_LINK_ENTRY_FULL_ID_INFORMATION, *PFILE_LINK_ENTRY_FULL_ID_INFORMATION;
|
|
|
|
typedef struct _FILE_LINKS_FULL_ID_INFORMATION {
|
|
ULONG BytesNeeded;
|
|
ULONG EntriesReturned;
|
|
FILE_LINK_ENTRY_FULL_ID_INFORMATION Entry;
|
|
} FILE_LINKS_FULL_ID_INFORMATION, *PFILE_LINKS_FULL_ID_INFORMATION;
|
|
|
|
#define FILE_RENAME_REPLACE_IF_EXISTS 0x001
|
|
#define FILE_RENAME_POSIX_SEMANTICS 0x002
|
|
#define FILE_RENAME_SUPPRESS_PIN_STATE_INHERITANCE 0x004
|
|
#define FILE_RENAME_SUPPRESS_STORAGE_RESERVE_INHERITANCE 0x008
|
|
#define FILE_RENAME_NO_INCREASE_AVAILABLE_SPACE 0x010
|
|
#define FILE_RENAME_NO_DECREASE_AVAILABLE_SPACE 0x020
|
|
#define FILE_RENAME_IGNORE_READONLY_ATTRIBUTE 0x040
|
|
#define FILE_RENAME_FORCE_RESIZE_TARGET_SR 0x080
|
|
#define FILE_RENAME_FORCE_RESIZE_SOURCE_SR 0x100
|
|
|
|
#define FILE_DISPOSITION_DELETE 0x1
|
|
#define FILE_DISPOSITION_POSIX_SEMANTICS 0x2
|
|
#define FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK 0x4
|
|
#define FILE_DISPOSITION_ON_CLOSE 0x8
|
|
|
|
#define FILE_LINK_REPLACE_IF_EXISTS 0x001
|
|
#define FILE_LINK_POSIX_SEMANTICS 0x002
|
|
#define FILE_LINK_SUPPRESS_STORAGE_RESERVE_INHERITANCE 0x008
|
|
#define FILE_LINK_NO_INCREASE_AVAILABLE_SPACE 0x010
|
|
#define FILE_LINK_NO_DECREASE_AVAILABLE_SPACE 0x020
|
|
#define FILE_LINK_IGNORE_READONLY_ATTRIBUTE 0x040
|
|
#define FILE_LINK_FORCE_RESIZE_TARGET_SR 0x080
|
|
#define FILE_LINK_FORCE_RESIZE_SOURCE_SR 0x100
|
|
|
|
#define FILE_CS_FLAG_CASE_SENSITIVE_DIR 1
|
|
|
|
#else
|
|
|
|
#define FILE_RENAME_INFORMATION_EX FILE_RENAME_INFORMATION
|
|
#define FILE_LINK_INFORMATION_EX FILE_LINK_INFORMATION
|
|
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef __REACTOS__
|
|
typedef struct _FILE_RENAME_INFORMATION_EX {
|
|
union {
|
|
BOOLEAN ReplaceIfExists;
|
|
ULONG Flags;
|
|
};
|
|
HANDLE RootDirectory;
|
|
ULONG FileNameLength;
|
|
WCHAR FileName[1];
|
|
} FILE_RENAME_INFORMATION_EX, *PFILE_RENAME_INFORMATION_EX;
|
|
|
|
typedef struct _FILE_DISPOSITION_INFORMATION_EX {
|
|
ULONG Flags;
|
|
} FILE_DISPOSITION_INFORMATION_EX, *PFILE_DISPOSITION_INFORMATION_EX;
|
|
|
|
typedef struct _FILE_LINK_INFORMATION_EX {
|
|
union {
|
|
BOOLEAN ReplaceIfExists;
|
|
ULONG Flags;
|
|
};
|
|
HANDLE RootDirectory;
|
|
ULONG FileNameLength;
|
|
WCHAR FileName[1];
|
|
} FILE_LINK_INFORMATION_EX, *PFILE_LINK_INFORMATION_EX;
|
|
|
|
#define FILE_RENAME_REPLACE_IF_EXISTS 0x001
|
|
#define FILE_RENAME_POSIX_SEMANTICS 0x002
|
|
#define FILE_RENAME_IGNORE_READONLY_ATTRIBUTE 0x040
|
|
|
|
#define FILE_DISPOSITION_DELETE 0x1
|
|
#define FILE_DISPOSITION_POSIX_SEMANTICS 0x2
|
|
#define FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK 0x4
|
|
|
|
#define FILE_LINK_REPLACE_IF_EXISTS 0x001
|
|
#define FILE_LINK_POSIX_SEMANTICS 0x002
|
|
#define FILE_LINK_IGNORE_READONLY_ATTRIBUTE 0x040
|
|
#endif
|
|
|
|
static NTSTATUS set_basic_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject) {
|
|
FILE_BASIC_INFORMATION* fbi = Irp->AssociatedIrp.SystemBuffer;
|
|
fcb* fcb = FileObject->FsContext;
|
|
ccb* ccb = FileObject->FsContext2;
|
|
file_ref* fileref = ccb ? ccb->fileref : NULL;
|
|
ULONG defda, filter = 0;
|
|
bool inode_item_changed = false;
|
|
NTSTATUS Status;
|
|
|
|
if (fcb->ads) {
|
|
if (fileref && fileref->parent)
|
|
fcb = fileref->parent->fcb;
|
|
else {
|
|
ERR("stream did not have fileref\n");
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
}
|
|
|
|
if (!ccb) {
|
|
ERR("ccb was NULL\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
TRACE("file = %p, attributes = %x\n", FileObject, fbi->FileAttributes);
|
|
|
|
ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);
|
|
|
|
if (fbi->FileAttributes & FILE_ATTRIBUTE_DIRECTORY && fcb->type != BTRFS_TYPE_DIRECTORY) {
|
|
WARN("attempted to set FILE_ATTRIBUTE_DIRECTORY on non-directory\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
if (fcb->inode == SUBVOL_ROOT_INODE && is_subvol_readonly(fcb->subvol, Irp) &&
|
|
(fbi->FileAttributes == 0 || fbi->FileAttributes & FILE_ATTRIBUTE_READONLY)) {
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
|
|
// don't allow readonly subvol to be made r/w if send operation running on it
|
|
if (fcb->inode == SUBVOL_ROOT_INODE && fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY &&
|
|
fcb->subvol->send_ops > 0) {
|
|
Status = STATUS_DEVICE_NOT_READY;
|
|
goto end;
|
|
}
|
|
|
|
// times of -2 are some sort of undocumented behaviour to do with LXSS
|
|
|
|
if (fbi->CreationTime.QuadPart == -2)
|
|
fbi->CreationTime.QuadPart = 0;
|
|
|
|
if (fbi->LastAccessTime.QuadPart == -2)
|
|
fbi->LastAccessTime.QuadPart = 0;
|
|
|
|
if (fbi->LastWriteTime.QuadPart == -2)
|
|
fbi->LastWriteTime.QuadPart = 0;
|
|
|
|
if (fbi->ChangeTime.QuadPart == -2)
|
|
fbi->ChangeTime.QuadPart = 0;
|
|
|
|
if (fbi->CreationTime.QuadPart == -1)
|
|
ccb->user_set_creation_time = true;
|
|
else if (fbi->CreationTime.QuadPart != 0) {
|
|
win_time_to_unix(fbi->CreationTime, &fcb->inode_item.otime);
|
|
inode_item_changed = true;
|
|
filter |= FILE_NOTIFY_CHANGE_CREATION;
|
|
|
|
ccb->user_set_creation_time = true;
|
|
}
|
|
|
|
if (fbi->LastAccessTime.QuadPart == -1)
|
|
ccb->user_set_access_time = true;
|
|
else if (fbi->LastAccessTime.QuadPart != 0) {
|
|
win_time_to_unix(fbi->LastAccessTime, &fcb->inode_item.st_atime);
|
|
inode_item_changed = true;
|
|
filter |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
|
|
|
|
ccb->user_set_access_time = true;
|
|
}
|
|
|
|
if (fbi->LastWriteTime.QuadPart == -1)
|
|
ccb->user_set_write_time = true;
|
|
else if (fbi->LastWriteTime.QuadPart != 0) {
|
|
win_time_to_unix(fbi->LastWriteTime, &fcb->inode_item.st_mtime);
|
|
inode_item_changed = true;
|
|
filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
|
|
|
|
ccb->user_set_write_time = true;
|
|
}
|
|
|
|
if (fbi->ChangeTime.QuadPart == -1)
|
|
ccb->user_set_change_time = true;
|
|
else if (fbi->ChangeTime.QuadPart != 0) {
|
|
win_time_to_unix(fbi->ChangeTime, &fcb->inode_item.st_ctime);
|
|
inode_item_changed = true;
|
|
// no filter for this
|
|
|
|
ccb->user_set_change_time = true;
|
|
}
|
|
|
|
// FileAttributes == 0 means don't set - undocumented, but seen in fastfat
|
|
if (fbi->FileAttributes != 0) {
|
|
LARGE_INTEGER time;
|
|
BTRFS_TIME now;
|
|
|
|
fbi->FileAttributes &= ~FILE_ATTRIBUTE_NORMAL;
|
|
|
|
defda = get_file_attributes(Vcb, fcb->subvol, fcb->inode, fcb->type, fileref && fileref->dc && fileref->dc->name.Length >= sizeof(WCHAR) && fileref->dc->name.Buffer[0] == '.',
|
|
true, Irp);
|
|
|
|
if (fcb->type == BTRFS_TYPE_DIRECTORY)
|
|
fbi->FileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
|
|
else if (fcb->type == BTRFS_TYPE_SYMLINK)
|
|
fbi->FileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT;
|
|
|
|
fcb->atts_changed = true;
|
|
|
|
if (fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT)
|
|
fbi->FileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT;
|
|
|
|
if (defda == fbi->FileAttributes)
|
|
fcb->atts_deleted = true;
|
|
else if (fcb->inode == SUBVOL_ROOT_INODE && (defda | FILE_ATTRIBUTE_READONLY) == (fbi->FileAttributes | FILE_ATTRIBUTE_READONLY))
|
|
fcb->atts_deleted = true;
|
|
|
|
fcb->atts = fbi->FileAttributes;
|
|
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &now);
|
|
|
|
if (!ccb->user_set_change_time)
|
|
fcb->inode_item.st_ctime = now;
|
|
|
|
fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
|
|
fcb->subvol->root_item.ctime = now;
|
|
|
|
if (fcb->inode == SUBVOL_ROOT_INODE) {
|
|
if (fbi->FileAttributes & FILE_ATTRIBUTE_READONLY)
|
|
fcb->subvol->root_item.flags |= BTRFS_SUBVOL_READONLY;
|
|
else
|
|
fcb->subvol->root_item.flags &= ~BTRFS_SUBVOL_READONLY;
|
|
}
|
|
|
|
inode_item_changed = true;
|
|
|
|
filter |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
|
|
}
|
|
|
|
if (inode_item_changed) {
|
|
fcb->inode_item.transid = Vcb->superblock.generation;
|
|
fcb->inode_item.sequence++;
|
|
fcb->inode_item_changed = true;
|
|
|
|
mark_fcb_dirty(fcb);
|
|
}
|
|
|
|
if (filter != 0)
|
|
queue_notification_fcb(fileref, filter, FILE_ACTION_MODIFIED, NULL);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
end:
|
|
ExReleaseResourceLite(fcb->Header.Resource);
|
|
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS set_disposition_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, bool ex) {
|
|
fcb* fcb = FileObject->FsContext;
|
|
ccb* ccb = FileObject->FsContext2;
|
|
file_ref* fileref = ccb ? ccb->fileref : NULL;
|
|
ULONG atts, flags;
|
|
NTSTATUS Status;
|
|
|
|
if (!fileref)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
if (ex) {
|
|
FILE_DISPOSITION_INFORMATION_EX* fdi = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
flags = fdi->Flags;
|
|
} else {
|
|
FILE_DISPOSITION_INFORMATION* fdi = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
flags = fdi->DeleteFile ? FILE_DISPOSITION_DELETE : 0;
|
|
}
|
|
|
|
ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);
|
|
|
|
TRACE("changing delete_on_close to %s for fcb %p\n", flags & FILE_DISPOSITION_DELETE ? "true" : "false", fcb);
|
|
|
|
if (fcb->ads) {
|
|
if (fileref->parent)
|
|
atts = fileref->parent->fcb->atts;
|
|
else {
|
|
ERR("no fileref for stream\n");
|
|
Status = STATUS_INTERNAL_ERROR;
|
|
goto end;
|
|
}
|
|
} else
|
|
atts = fcb->atts;
|
|
|
|
TRACE("atts = %x\n", atts);
|
|
|
|
if (atts & FILE_ATTRIBUTE_READONLY) {
|
|
TRACE("not allowing readonly file to be deleted\n");
|
|
Status = STATUS_CANNOT_DELETE;
|
|
goto end;
|
|
}
|
|
|
|
if (fcb->inode == SUBVOL_ROOT_INODE && fcb->subvol->id == BTRFS_ROOT_FSTREE) {
|
|
WARN("not allowing \\$Root to be deleted\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
|
|
// FIXME - can we skip this bit for subvols?
|
|
if (fcb->type == BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0 && (!fileref || fileref->fcb != Vcb->dummy_fcb)) {
|
|
TRACE("directory not empty\n");
|
|
Status = STATUS_DIRECTORY_NOT_EMPTY;
|
|
goto end;
|
|
}
|
|
|
|
if (!MmFlushImageSection(&fcb->nonpaged->segment_object, MmFlushForDelete)) {
|
|
if (!ex || flags & FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK) {
|
|
TRACE("trying to delete file which is being mapped as an image\n");
|
|
Status = STATUS_CANNOT_DELETE;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
ccb->fileref->delete_on_close = flags & FILE_DISPOSITION_DELETE;
|
|
|
|
FileObject->DeletePending = flags & FILE_DISPOSITION_DELETE;
|
|
|
|
if (flags & FILE_DISPOSITION_DELETE && flags & FILE_DISPOSITION_POSIX_SEMANTICS)
|
|
ccb->fileref->posix_delete = true;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
end:
|
|
ExReleaseResourceLite(fcb->Header.Resource);
|
|
|
|
// send notification that directory is about to be deleted
|
|
if (NT_SUCCESS(Status) && flags & FILE_DISPOSITION_DELETE && fcb->type == BTRFS_TYPE_DIRECTORY) {
|
|
FsRtlNotifyFullChangeDirectory(Vcb->NotifySync, &Vcb->DirNotifyList, FileObject->FsContext,
|
|
NULL, false, false, 0, NULL, NULL, NULL);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
bool has_open_children(file_ref* fileref) {
|
|
LIST_ENTRY* le = fileref->children.Flink;
|
|
|
|
if (IsListEmpty(&fileref->children))
|
|
return false;
|
|
|
|
while (le != &fileref->children) {
|
|
file_ref* c = CONTAINING_RECORD(le, file_ref, list_entry);
|
|
|
|
if (c->open_count > 0)
|
|
return true;
|
|
|
|
if (has_open_children(c))
|
|
return true;
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static NTSTATUS duplicate_fcb(fcb* oldfcb, fcb** pfcb) {
|
|
device_extension* Vcb = oldfcb->Vcb;
|
|
fcb* fcb;
|
|
LIST_ENTRY* le;
|
|
|
|
// FIXME - we can skip a lot of this if the inode is about to be deleted
|
|
|
|
fcb = create_fcb(Vcb, PagedPool); // FIXME - what if we duplicate the paging file?
|
|
if (!fcb) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
fcb->Vcb = Vcb;
|
|
|
|
fcb->Header.IsFastIoPossible = fast_io_possible(fcb);
|
|
fcb->Header.AllocationSize = oldfcb->Header.AllocationSize;
|
|
fcb->Header.FileSize = oldfcb->Header.FileSize;
|
|
fcb->Header.ValidDataLength = oldfcb->Header.ValidDataLength;
|
|
|
|
fcb->type = oldfcb->type;
|
|
|
|
if (oldfcb->ads) {
|
|
fcb->ads = true;
|
|
fcb->adshash = oldfcb->adshash;
|
|
fcb->adsmaxlen = oldfcb->adsmaxlen;
|
|
|
|
if (oldfcb->adsxattr.Buffer && oldfcb->adsxattr.Length > 0) {
|
|
fcb->adsxattr.Length = oldfcb->adsxattr.Length;
|
|
fcb->adsxattr.MaximumLength = fcb->adsxattr.Length + 1;
|
|
fcb->adsxattr.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->adsxattr.MaximumLength, ALLOC_TAG);
|
|
|
|
if (!fcb->adsxattr.Buffer) {
|
|
ERR("out of memory\n");
|
|
free_fcb(fcb);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(fcb->adsxattr.Buffer, oldfcb->adsxattr.Buffer, fcb->adsxattr.Length);
|
|
fcb->adsxattr.Buffer[fcb->adsxattr.Length] = 0;
|
|
}
|
|
|
|
if (oldfcb->adsdata.Buffer && oldfcb->adsdata.Length > 0) {
|
|
fcb->adsdata.Length = fcb->adsdata.MaximumLength = oldfcb->adsdata.Length;
|
|
fcb->adsdata.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->adsdata.MaximumLength, ALLOC_TAG);
|
|
|
|
if (!fcb->adsdata.Buffer) {
|
|
ERR("out of memory\n");
|
|
free_fcb(fcb);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(fcb->adsdata.Buffer, oldfcb->adsdata.Buffer, fcb->adsdata.Length);
|
|
}
|
|
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(&fcb->inode_item, &oldfcb->inode_item, sizeof(INODE_ITEM));
|
|
fcb->inode_item_changed = true;
|
|
|
|
if (oldfcb->sd && RtlLengthSecurityDescriptor(oldfcb->sd) > 0) {
|
|
fcb->sd = ExAllocatePoolWithTag(PagedPool, RtlLengthSecurityDescriptor(oldfcb->sd), ALLOC_TAG);
|
|
if (!fcb->sd) {
|
|
ERR("out of memory\n");
|
|
free_fcb(fcb);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(fcb->sd, oldfcb->sd, RtlLengthSecurityDescriptor(oldfcb->sd));
|
|
}
|
|
|
|
fcb->atts = oldfcb->atts;
|
|
|
|
le = oldfcb->extents.Flink;
|
|
while (le != &oldfcb->extents) {
|
|
extent* ext = CONTAINING_RECORD(le, extent, list_entry);
|
|
|
|
if (!ext->ignore) {
|
|
extent* ext2 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG);
|
|
|
|
if (!ext2) {
|
|
ERR("out of memory\n");
|
|
free_fcb(fcb);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
ext2->offset = ext->offset;
|
|
ext2->datalen = ext->datalen;
|
|
|
|
if (ext2->datalen > 0)
|
|
RtlCopyMemory(&ext2->extent_data, &ext->extent_data, ext2->datalen);
|
|
|
|
ext2->unique = false;
|
|
ext2->ignore = false;
|
|
ext2->inserted = true;
|
|
|
|
if (ext->csum) {
|
|
ULONG len;
|
|
EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;
|
|
|
|
if (ext->extent_data.compression == BTRFS_COMPRESSION_NONE)
|
|
len = (ULONG)ed2->num_bytes;
|
|
else
|
|
len = (ULONG)ed2->size;
|
|
|
|
len = len * sizeof(uint32_t) / Vcb->superblock.sector_size;
|
|
|
|
ext2->csum = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG);
|
|
if (!ext2->csum) {
|
|
ERR("out of memory\n");
|
|
free_fcb(fcb);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(ext2->csum, ext->csum, len);
|
|
} else
|
|
ext2->csum = NULL;
|
|
|
|
InsertTailList(&fcb->extents, &ext2->list_entry);
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
le = oldfcb->hardlinks.Flink;
|
|
while (le != &oldfcb->hardlinks) {
|
|
hardlink *hl = CONTAINING_RECORD(le, hardlink, list_entry), *hl2;
|
|
|
|
hl2 = ExAllocatePoolWithTag(PagedPool, sizeof(hardlink), ALLOC_TAG);
|
|
|
|
if (!hl2) {
|
|
ERR("out of memory\n");
|
|
free_fcb(fcb);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
hl2->parent = hl->parent;
|
|
hl2->index = hl->index;
|
|
|
|
hl2->name.Length = hl2->name.MaximumLength = hl->name.Length;
|
|
hl2->name.Buffer = ExAllocatePoolWithTag(PagedPool, hl2->name.MaximumLength, ALLOC_TAG);
|
|
|
|
if (!hl2->name.Buffer) {
|
|
ERR("out of memory\n");
|
|
ExFreePool(hl2);
|
|
free_fcb(fcb);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(hl2->name.Buffer, hl->name.Buffer, hl->name.Length);
|
|
|
|
hl2->utf8.Length = hl2->utf8.MaximumLength = hl->utf8.Length;
|
|
hl2->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, hl2->utf8.MaximumLength, ALLOC_TAG);
|
|
|
|
if (!hl2->utf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
ExFreePool(hl2->name.Buffer);
|
|
ExFreePool(hl2);
|
|
free_fcb(fcb);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(hl2->utf8.Buffer, hl->utf8.Buffer, hl->utf8.Length);
|
|
|
|
InsertTailList(&fcb->hardlinks, &hl2->list_entry);
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
if (oldfcb->reparse_xattr.Buffer && oldfcb->reparse_xattr.Length > 0) {
|
|
fcb->reparse_xattr.Length = fcb->reparse_xattr.MaximumLength = oldfcb->reparse_xattr.Length;
|
|
|
|
fcb->reparse_xattr.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->reparse_xattr.MaximumLength, ALLOC_TAG);
|
|
if (!fcb->reparse_xattr.Buffer) {
|
|
ERR("out of memory\n");
|
|
free_fcb(fcb);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(fcb->reparse_xattr.Buffer, oldfcb->reparse_xattr.Buffer, fcb->reparse_xattr.Length);
|
|
}
|
|
|
|
if (oldfcb->ea_xattr.Buffer && oldfcb->ea_xattr.Length > 0) {
|
|
fcb->ea_xattr.Length = fcb->ea_xattr.MaximumLength = oldfcb->ea_xattr.Length;
|
|
|
|
fcb->ea_xattr.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->ea_xattr.MaximumLength, ALLOC_TAG);
|
|
if (!fcb->ea_xattr.Buffer) {
|
|
ERR("out of memory\n");
|
|
free_fcb(fcb);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(fcb->ea_xattr.Buffer, oldfcb->ea_xattr.Buffer, fcb->ea_xattr.Length);
|
|
}
|
|
|
|
fcb->prop_compression = oldfcb->prop_compression;
|
|
|
|
le = oldfcb->xattrs.Flink;
|
|
while (le != &oldfcb->xattrs) {
|
|
xattr* xa = CONTAINING_RECORD(le, xattr, list_entry);
|
|
|
|
if (xa->valuelen > 0) {
|
|
xattr* xa2;
|
|
|
|
xa2 = ExAllocatePoolWithTag(PagedPool, offsetof(xattr, data[0]) + xa->namelen + xa->valuelen, ALLOC_TAG);
|
|
|
|
if (!xa2) {
|
|
ERR("out of memory\n");
|
|
free_fcb(fcb);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
xa2->namelen = xa->namelen;
|
|
xa2->valuelen = xa->valuelen;
|
|
xa2->dirty = xa->dirty;
|
|
memcpy(xa2->data, xa->data, xa->namelen + xa->valuelen);
|
|
|
|
InsertTailList(&fcb->xattrs, &xa2->list_entry);
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
end:
|
|
*pfcb = fcb;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
typedef struct _move_entry {
|
|
file_ref* fileref;
|
|
fcb* dummyfcb;
|
|
file_ref* dummyfileref;
|
|
struct _move_entry* parent;
|
|
LIST_ENTRY list_entry;
|
|
} move_entry;
|
|
|
|
static NTSTATUS add_children_to_move_list(device_extension* Vcb, move_entry* me, PIRP Irp) {
|
|
NTSTATUS Status;
|
|
LIST_ENTRY* le;
|
|
|
|
ExAcquireResourceSharedLite(&me->fileref->fcb->nonpaged->dir_children_lock, true);
|
|
|
|
le = me->fileref->fcb->dir_children_index.Flink;
|
|
|
|
while (le != &me->fileref->fcb->dir_children_index) {
|
|
dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_index);
|
|
file_ref* fr;
|
|
move_entry* me2;
|
|
|
|
Status = open_fileref_child(Vcb, me->fileref, &dc->name, true, true, dc->index == 0 ? true : false, PagedPool, &fr, Irp);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("open_fileref_child returned %08x\n", Status);
|
|
ExReleaseResourceLite(&me->fileref->fcb->nonpaged->dir_children_lock);
|
|
return Status;
|
|
}
|
|
|
|
me2 = ExAllocatePoolWithTag(PagedPool, sizeof(move_entry), ALLOC_TAG);
|
|
if (!me2) {
|
|
ERR("out of memory\n");
|
|
ExReleaseResourceLite(&me->fileref->fcb->nonpaged->dir_children_lock);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
me2->fileref = fr;
|
|
me2->dummyfcb = NULL;
|
|
me2->dummyfileref = NULL;
|
|
me2->parent = me;
|
|
|
|
InsertHeadList(&me->list_entry, &me2->list_entry);
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
ExReleaseResourceLite(&me->fileref->fcb->nonpaged->dir_children_lock);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
void remove_dir_child_from_hash_lists(fcb* fcb, dir_child* dc) {
|
|
uint8_t c;
|
|
|
|
c = dc->hash >> 24;
|
|
|
|
if (fcb->hash_ptrs[c] == &dc->list_entry_hash) {
|
|
if (dc->list_entry_hash.Flink == &fcb->dir_children_hash)
|
|
fcb->hash_ptrs[c] = NULL;
|
|
else {
|
|
dir_child* dc2 = CONTAINING_RECORD(dc->list_entry_hash.Flink, dir_child, list_entry_hash);
|
|
|
|
if (dc2->hash >> 24 == c)
|
|
fcb->hash_ptrs[c] = &dc2->list_entry_hash;
|
|
else
|
|
fcb->hash_ptrs[c] = NULL;
|
|
}
|
|
}
|
|
|
|
RemoveEntryList(&dc->list_entry_hash);
|
|
|
|
c = dc->hash_uc >> 24;
|
|
|
|
if (fcb->hash_ptrs_uc[c] == &dc->list_entry_hash_uc) {
|
|
if (dc->list_entry_hash_uc.Flink == &fcb->dir_children_hash_uc)
|
|
fcb->hash_ptrs_uc[c] = NULL;
|
|
else {
|
|
dir_child* dc2 = CONTAINING_RECORD(dc->list_entry_hash_uc.Flink, dir_child, list_entry_hash_uc);
|
|
|
|
if (dc2->hash_uc >> 24 == c)
|
|
fcb->hash_ptrs_uc[c] = &dc2->list_entry_hash_uc;
|
|
else
|
|
fcb->hash_ptrs_uc[c] = NULL;
|
|
}
|
|
}
|
|
|
|
RemoveEntryList(&dc->list_entry_hash_uc);
|
|
}
|
|
|
|
static NTSTATUS create_directory_fcb(device_extension* Vcb, root* r, fcb* parfcb, fcb** pfcb) {
|
|
NTSTATUS Status;
|
|
fcb* fcb;
|
|
SECURITY_SUBJECT_CONTEXT subjcont;
|
|
PSID owner;
|
|
BOOLEAN defaulted;
|
|
LARGE_INTEGER time;
|
|
BTRFS_TIME now;
|
|
|
|
fcb = create_fcb(Vcb, PagedPool);
|
|
if (!fcb) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &now);
|
|
|
|
fcb->Vcb = Vcb;
|
|
|
|
fcb->subvol = r;
|
|
fcb->inode = InterlockedIncrement64(&r->lastinode);
|
|
fcb->type = BTRFS_TYPE_DIRECTORY;
|
|
|
|
fcb->inode_item.generation = Vcb->superblock.generation;
|
|
fcb->inode_item.transid = Vcb->superblock.generation;
|
|
fcb->inode_item.st_nlink = 1;
|
|
fcb->inode_item.st_mode = __S_IFDIR | inherit_mode(parfcb, true);
|
|
fcb->inode_item.st_atime = fcb->inode_item.st_ctime = fcb->inode_item.st_mtime = fcb->inode_item.otime = now;
|
|
fcb->inode_item.st_gid = GID_NOBODY;
|
|
|
|
fcb->atts = get_file_attributes(Vcb, fcb->subvol, fcb->inode, fcb->type, false, true, NULL);
|
|
|
|
SeCaptureSubjectContext(&subjcont);
|
|
|
|
Status = SeAssignSecurity(parfcb->sd, NULL, (void**)&fcb->sd, true, &subjcont, IoGetFileObjectGenericMapping(), PagedPool);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
reap_fcb(fcb);
|
|
ERR("SeAssignSecurity returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (!fcb->sd) {
|
|
reap_fcb(fcb);
|
|
ERR("SeAssignSecurity returned NULL security descriptor\n");
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
Status = RtlGetOwnerSecurityDescriptor(fcb->sd, &owner, &defaulted);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("RtlGetOwnerSecurityDescriptor returned %08x\n", Status);
|
|
fcb->inode_item.st_uid = UID_NOBODY;
|
|
fcb->sd_dirty = true;
|
|
} else {
|
|
fcb->inode_item.st_uid = sid_to_uid(owner);
|
|
fcb->sd_dirty = fcb->inode_item.st_uid == UID_NOBODY;
|
|
}
|
|
|
|
find_gid(fcb, parfcb, &subjcont);
|
|
|
|
fcb->inode_item_changed = true;
|
|
|
|
fcb->Header.IsFastIoPossible = fast_io_possible(fcb);
|
|
fcb->Header.AllocationSize.QuadPart = 0;
|
|
fcb->Header.FileSize.QuadPart = 0;
|
|
fcb->Header.ValidDataLength.QuadPart = 0;
|
|
|
|
fcb->created = true;
|
|
|
|
if (parfcb->inode_item.flags & BTRFS_INODE_COMPRESS)
|
|
fcb->inode_item.flags |= BTRFS_INODE_COMPRESS;
|
|
|
|
fcb->prop_compression = parfcb->prop_compression;
|
|
fcb->prop_compression_changed = fcb->prop_compression != PropCompression_None;
|
|
|
|
fcb->hash_ptrs = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG);
|
|
if (!fcb->hash_ptrs) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlZeroMemory(fcb->hash_ptrs, sizeof(LIST_ENTRY*) * 256);
|
|
|
|
fcb->hash_ptrs_uc = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG);
|
|
if (!fcb->hash_ptrs_uc) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlZeroMemory(fcb->hash_ptrs_uc, sizeof(LIST_ENTRY*) * 256);
|
|
|
|
acquire_fcb_lock_exclusive(Vcb);
|
|
InsertTailList(&r->fcbs, &fcb->list_entry);
|
|
InsertTailList(&Vcb->all_fcbs, &fcb->list_entry_all);
|
|
r->fcbs_version++;
|
|
release_fcb_lock(Vcb);
|
|
|
|
mark_fcb_dirty(fcb);
|
|
|
|
*pfcb = fcb;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS move_across_subvols(file_ref* fileref, ccb* ccb, file_ref* destdir, PANSI_STRING utf8, PUNICODE_STRING fnus, PIRP Irp, LIST_ENTRY* rollback) {
|
|
NTSTATUS Status;
|
|
LIST_ENTRY move_list, *le;
|
|
move_entry* me;
|
|
LARGE_INTEGER time;
|
|
BTRFS_TIME now;
|
|
file_ref* origparent;
|
|
|
|
// FIXME - make sure me->dummyfileref and me->dummyfcb get freed properly
|
|
|
|
InitializeListHead(&move_list);
|
|
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &now);
|
|
|
|
acquire_fcb_lock_exclusive(fileref->fcb->Vcb);
|
|
|
|
me = ExAllocatePoolWithTag(PagedPool, sizeof(move_entry), ALLOC_TAG);
|
|
|
|
if (!me) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
origparent = fileref->parent;
|
|
|
|
me->fileref = fileref;
|
|
increase_fileref_refcount(me->fileref);
|
|
me->dummyfcb = NULL;
|
|
me->dummyfileref = NULL;
|
|
me->parent = NULL;
|
|
|
|
InsertTailList(&move_list, &me->list_entry);
|
|
|
|
le = move_list.Flink;
|
|
while (le != &move_list) {
|
|
me = CONTAINING_RECORD(le, move_entry, list_entry);
|
|
|
|
ExAcquireResourceSharedLite(me->fileref->fcb->Header.Resource, true);
|
|
|
|
if (!me->fileref->fcb->ads && me->fileref->fcb->subvol == origparent->fcb->subvol) {
|
|
Status = add_children_to_move_list(fileref->fcb->Vcb, me, Irp);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("add_children_to_move_list returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
ExReleaseResourceLite(me->fileref->fcb->Header.Resource);
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
send_notification_fileref(fileref, fileref->fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_REMOVED, NULL);
|
|
|
|
// loop through list and create new inodes
|
|
|
|
le = move_list.Flink;
|
|
while (le != &move_list) {
|
|
me = CONTAINING_RECORD(le, move_entry, list_entry);
|
|
|
|
if (me->fileref->fcb->inode != SUBVOL_ROOT_INODE && me->fileref->fcb != fileref->fcb->Vcb->dummy_fcb) {
|
|
if (!me->dummyfcb) {
|
|
ULONG defda;
|
|
bool inserted = false;
|
|
LIST_ENTRY* le3;
|
|
|
|
ExAcquireResourceExclusiveLite(me->fileref->fcb->Header.Resource, true);
|
|
|
|
Status = duplicate_fcb(me->fileref->fcb, &me->dummyfcb);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("duplicate_fcb returned %08x\n", Status);
|
|
ExReleaseResourceLite(me->fileref->fcb->Header.Resource);
|
|
goto end;
|
|
}
|
|
|
|
me->dummyfcb->subvol = me->fileref->fcb->subvol;
|
|
me->dummyfcb->inode = me->fileref->fcb->inode;
|
|
|
|
if (!me->dummyfcb->ads) {
|
|
me->dummyfcb->sd_dirty = me->fileref->fcb->sd_dirty;
|
|
me->dummyfcb->atts_changed = me->fileref->fcb->atts_changed;
|
|
me->dummyfcb->atts_deleted = me->fileref->fcb->atts_deleted;
|
|
me->dummyfcb->extents_changed = me->fileref->fcb->extents_changed;
|
|
me->dummyfcb->reparse_xattr_changed = me->fileref->fcb->reparse_xattr_changed;
|
|
me->dummyfcb->ea_changed = me->fileref->fcb->ea_changed;
|
|
}
|
|
|
|
me->dummyfcb->created = me->fileref->fcb->created;
|
|
me->dummyfcb->deleted = me->fileref->fcb->deleted;
|
|
mark_fcb_dirty(me->dummyfcb);
|
|
|
|
if (!me->fileref->fcb->ads) {
|
|
LIST_ENTRY* le2;
|
|
|
|
me->fileref->fcb->subvol = destdir->fcb->subvol;
|
|
me->fileref->fcb->inode = InterlockedIncrement64(&destdir->fcb->subvol->lastinode);
|
|
me->fileref->fcb->inode_item.st_nlink = 1;
|
|
|
|
defda = get_file_attributes(me->fileref->fcb->Vcb, me->fileref->fcb->subvol, me->fileref->fcb->inode,
|
|
me->fileref->fcb->type, me->fileref->dc && me->fileref->dc->name.Length >= sizeof(WCHAR) && me->fileref->dc->name.Buffer[0] == '.',
|
|
true, Irp);
|
|
|
|
me->fileref->fcb->sd_dirty = !!me->fileref->fcb->sd;
|
|
me->fileref->fcb->atts_changed = defda != me->fileref->fcb->atts;
|
|
me->fileref->fcb->extents_changed = !IsListEmpty(&me->fileref->fcb->extents);
|
|
me->fileref->fcb->reparse_xattr_changed = !!me->fileref->fcb->reparse_xattr.Buffer;
|
|
me->fileref->fcb->ea_changed = !!me->fileref->fcb->ea_xattr.Buffer;
|
|
me->fileref->fcb->xattrs_changed = !IsListEmpty(&me->fileref->fcb->xattrs);
|
|
me->fileref->fcb->inode_item_changed = true;
|
|
|
|
le2 = me->fileref->fcb->xattrs.Flink;
|
|
while (le2 != &me->fileref->fcb->xattrs) {
|
|
xattr* xa = CONTAINING_RECORD(le2, xattr, list_entry);
|
|
|
|
xa->dirty = true;
|
|
|
|
le2 = le2->Flink;
|
|
}
|
|
|
|
if (le == move_list.Flink) { // first entry
|
|
me->fileref->fcb->inode_item.transid = me->fileref->fcb->Vcb->superblock.generation;
|
|
me->fileref->fcb->inode_item.sequence++;
|
|
|
|
if (!ccb->user_set_change_time)
|
|
me->fileref->fcb->inode_item.st_ctime = now;
|
|
}
|
|
|
|
le2 = me->fileref->fcb->extents.Flink;
|
|
while (le2 != &me->fileref->fcb->extents) {
|
|
extent* ext = CONTAINING_RECORD(le2, extent, list_entry);
|
|
|
|
if (!ext->ignore && (ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC)) {
|
|
EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;
|
|
|
|
if (ed2->size != 0) {
|
|
chunk* c = get_chunk_from_address(me->fileref->fcb->Vcb, ed2->address);
|
|
|
|
if (!c) {
|
|
ERR("get_chunk_from_address(%I64x) failed\n", ed2->address);
|
|
} else {
|
|
Status = update_changed_extent_ref(me->fileref->fcb->Vcb, c, ed2->address, ed2->size, me->fileref->fcb->subvol->id, me->fileref->fcb->inode,
|
|
ext->offset - ed2->offset, 1, me->fileref->fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("update_changed_extent_ref returned %08x\n", Status);
|
|
ExReleaseResourceLite(me->fileref->fcb->Header.Resource);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
le2 = le2->Flink;
|
|
}
|
|
} else {
|
|
me->fileref->fcb->subvol = me->parent->fileref->fcb->subvol;
|
|
me->fileref->fcb->inode = me->parent->fileref->fcb->inode;
|
|
}
|
|
|
|
me->fileref->fcb->created = true;
|
|
|
|
InsertHeadList(&me->fileref->fcb->list_entry, &me->dummyfcb->list_entry);
|
|
RemoveEntryList(&me->fileref->fcb->list_entry);
|
|
|
|
le3 = destdir->fcb->subvol->fcbs.Flink;
|
|
while (le3 != &destdir->fcb->subvol->fcbs) {
|
|
fcb* fcb = CONTAINING_RECORD(le3, struct _fcb, list_entry);
|
|
|
|
if (fcb->inode > me->fileref->fcb->inode) {
|
|
InsertHeadList(le3->Blink, &me->fileref->fcb->list_entry);
|
|
inserted = true;
|
|
break;
|
|
}
|
|
|
|
le3 = le3->Flink;
|
|
}
|
|
|
|
if (!inserted)
|
|
InsertTailList(&destdir->fcb->subvol->fcbs, &me->fileref->fcb->list_entry);
|
|
|
|
InsertTailList(&me->fileref->fcb->Vcb->all_fcbs, &me->dummyfcb->list_entry_all);
|
|
|
|
while (!IsListEmpty(&me->fileref->fcb->hardlinks)) {
|
|
hardlink* hl = CONTAINING_RECORD(RemoveHeadList(&me->fileref->fcb->hardlinks), hardlink, list_entry);
|
|
|
|
if (hl->name.Buffer)
|
|
ExFreePool(hl->name.Buffer);
|
|
|
|
if (hl->utf8.Buffer)
|
|
ExFreePool(hl->utf8.Buffer);
|
|
|
|
ExFreePool(hl);
|
|
}
|
|
|
|
me->fileref->fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(me->fileref->fcb);
|
|
|
|
if ((!me->dummyfcb->ads && me->dummyfcb->inode_item.st_nlink > 1) || (me->dummyfcb->ads && me->parent->dummyfcb->inode_item.st_nlink > 1)) {
|
|
LIST_ENTRY* le2 = le->Flink;
|
|
|
|
while (le2 != &move_list) {
|
|
move_entry* me2 = CONTAINING_RECORD(le2, move_entry, list_entry);
|
|
|
|
if (me2->fileref->fcb == me->fileref->fcb && !me2->fileref->fcb->ads) {
|
|
me2->dummyfcb = me->dummyfcb;
|
|
InterlockedIncrement(&me->dummyfcb->refcount);
|
|
}
|
|
|
|
le2 = le2->Flink;
|
|
}
|
|
}
|
|
|
|
ExReleaseResourceLite(me->fileref->fcb->Header.Resource);
|
|
} else {
|
|
ExAcquireResourceExclusiveLite(me->fileref->fcb->Header.Resource, true);
|
|
me->fileref->fcb->inode_item.st_nlink++;
|
|
me->fileref->fcb->inode_item_changed = true;
|
|
ExReleaseResourceLite(me->fileref->fcb->Header.Resource);
|
|
}
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
fileref->fcb->subvol->root_item.ctransid = fileref->fcb->Vcb->superblock.generation;
|
|
fileref->fcb->subvol->root_item.ctime = now;
|
|
|
|
// loop through list and create new filerefs
|
|
|
|
le = move_list.Flink;
|
|
while (le != &move_list) {
|
|
hardlink* hl;
|
|
bool name_changed = false;
|
|
|
|
me = CONTAINING_RECORD(le, move_entry, list_entry);
|
|
|
|
me->dummyfileref = create_fileref(fileref->fcb->Vcb);
|
|
if (!me->dummyfileref) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
if (me->fileref->fcb == me->fileref->fcb->Vcb->dummy_fcb) {
|
|
root* r = me->parent ? me->parent->fileref->fcb->subvol : destdir->fcb->subvol;
|
|
|
|
Status = create_directory_fcb(me->fileref->fcb->Vcb, r, me->fileref->parent->fcb, &me->fileref->fcb);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("create_directory_fcb returnd %08x\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
me->fileref->dc->key.obj_id = me->fileref->fcb->inode;
|
|
me->fileref->dc->key.obj_type = TYPE_INODE_ITEM;
|
|
|
|
me->dummyfileref->fcb = me->fileref->fcb->Vcb->dummy_fcb;
|
|
} else if (me->fileref->fcb->inode == SUBVOL_ROOT_INODE) {
|
|
me->dummyfileref->fcb = me->fileref->fcb;
|
|
|
|
me->fileref->fcb->subvol->parent = le == move_list.Flink ? destdir->fcb->subvol->id : me->parent->fileref->fcb->subvol->id;
|
|
} else
|
|
me->dummyfileref->fcb = me->dummyfcb;
|
|
|
|
InterlockedIncrement(&me->dummyfileref->fcb->refcount);
|
|
|
|
me->dummyfileref->oldutf8 = me->fileref->oldutf8;
|
|
me->dummyfileref->oldindex = me->fileref->dc->index;
|
|
|
|
if (le == move_list.Flink && (me->fileref->dc->utf8.Length != utf8->Length || RtlCompareMemory(me->fileref->dc->utf8.Buffer, utf8->Buffer, utf8->Length) != utf8->Length))
|
|
name_changed = true;
|
|
|
|
if ((le == move_list.Flink || me->fileref->fcb->inode == SUBVOL_ROOT_INODE) && !me->dummyfileref->oldutf8.Buffer) {
|
|
me->dummyfileref->oldutf8.Buffer = ExAllocatePoolWithTag(PagedPool, me->fileref->dc->utf8.Length, ALLOC_TAG);
|
|
if (!me->dummyfileref->oldutf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(me->dummyfileref->oldutf8.Buffer, me->fileref->dc->utf8.Buffer, me->fileref->dc->utf8.Length);
|
|
|
|
me->dummyfileref->oldutf8.Length = me->dummyfileref->oldutf8.MaximumLength = me->fileref->dc->utf8.Length;
|
|
}
|
|
|
|
me->dummyfileref->delete_on_close = me->fileref->delete_on_close;
|
|
me->dummyfileref->deleted = me->fileref->deleted;
|
|
|
|
me->dummyfileref->created = me->fileref->created;
|
|
me->fileref->created = true;
|
|
|
|
me->dummyfileref->parent = me->parent ? me->parent->dummyfileref : origparent;
|
|
increase_fileref_refcount(me->dummyfileref->parent);
|
|
|
|
ExAcquireResourceExclusiveLite(&me->dummyfileref->parent->fcb->nonpaged->dir_children_lock, true);
|
|
InsertTailList(&me->dummyfileref->parent->children, &me->dummyfileref->list_entry);
|
|
ExReleaseResourceLite(&me->dummyfileref->parent->fcb->nonpaged->dir_children_lock);
|
|
|
|
if (me->dummyfileref->fcb->type == BTRFS_TYPE_DIRECTORY)
|
|
me->dummyfileref->fcb->fileref = me->dummyfileref;
|
|
|
|
if (!me->parent) {
|
|
RemoveEntryList(&me->fileref->list_entry);
|
|
|
|
increase_fileref_refcount(destdir);
|
|
|
|
if (me->fileref->dc) {
|
|
// remove from old parent
|
|
ExAcquireResourceExclusiveLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock, true);
|
|
RemoveEntryList(&me->fileref->dc->list_entry_index);
|
|
remove_dir_child_from_hash_lists(me->fileref->parent->fcb, me->fileref->dc);
|
|
ExReleaseResourceLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock);
|
|
|
|
me->fileref->parent->fcb->inode_item.st_size -= me->fileref->dc->utf8.Length * 2;
|
|
me->fileref->parent->fcb->inode_item.transid = me->fileref->fcb->Vcb->superblock.generation;
|
|
me->fileref->parent->fcb->inode_item.sequence++;
|
|
me->fileref->parent->fcb->inode_item.st_ctime = now;
|
|
me->fileref->parent->fcb->inode_item.st_mtime = now;
|
|
me->fileref->parent->fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(me->fileref->parent->fcb);
|
|
|
|
if (name_changed) {
|
|
ExFreePool(me->fileref->dc->utf8.Buffer);
|
|
ExFreePool(me->fileref->dc->name.Buffer);
|
|
ExFreePool(me->fileref->dc->name_uc.Buffer);
|
|
|
|
me->fileref->dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8->Length, ALLOC_TAG);
|
|
if (!me->fileref->dc->utf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
me->fileref->dc->utf8.Length = me->fileref->dc->utf8.MaximumLength = utf8->Length;
|
|
RtlCopyMemory(me->fileref->dc->utf8.Buffer, utf8->Buffer, utf8->Length);
|
|
|
|
me->fileref->dc->name.Buffer = ExAllocatePoolWithTag(PagedPool, fnus->Length, ALLOC_TAG);
|
|
if (!me->fileref->dc->name.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
me->fileref->dc->name.Length = me->fileref->dc->name.MaximumLength = fnus->Length;
|
|
RtlCopyMemory(me->fileref->dc->name.Buffer, fnus->Buffer, fnus->Length);
|
|
|
|
Status = RtlUpcaseUnicodeString(&fileref->dc->name_uc, &fileref->dc->name, true);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("RtlUpcaseUnicodeString returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
me->fileref->dc->hash = calc_crc32c(0xffffffff, (uint8_t*)me->fileref->dc->name.Buffer, me->fileref->dc->name.Length);
|
|
me->fileref->dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)me->fileref->dc->name_uc.Buffer, me->fileref->dc->name_uc.Length);
|
|
}
|
|
|
|
if (me->fileref->dc->key.obj_type == TYPE_INODE_ITEM)
|
|
me->fileref->dc->key.obj_id = me->fileref->fcb->inode;
|
|
|
|
// add to new parent
|
|
|
|
ExAcquireResourceExclusiveLite(&destdir->fcb->nonpaged->dir_children_lock, true);
|
|
|
|
if (IsListEmpty(&destdir->fcb->dir_children_index))
|
|
me->fileref->dc->index = 2;
|
|
else {
|
|
dir_child* dc2 = CONTAINING_RECORD(destdir->fcb->dir_children_index.Blink, dir_child, list_entry_index);
|
|
|
|
me->fileref->dc->index = max(2, dc2->index + 1);
|
|
}
|
|
|
|
InsertTailList(&destdir->fcb->dir_children_index, &me->fileref->dc->list_entry_index);
|
|
insert_dir_child_into_hash_lists(destdir->fcb, me->fileref->dc);
|
|
ExReleaseResourceLite(&destdir->fcb->nonpaged->dir_children_lock);
|
|
}
|
|
|
|
free_fileref(me->fileref->parent);
|
|
me->fileref->parent = destdir;
|
|
|
|
ExAcquireResourceExclusiveLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock, true);
|
|
InsertTailList(&me->fileref->parent->children, &me->fileref->list_entry);
|
|
ExReleaseResourceLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock);
|
|
|
|
TRACE("me->fileref->parent->fcb->inode_item.st_size (inode %I64x) was %I64x\n", me->fileref->parent->fcb->inode, me->fileref->parent->fcb->inode_item.st_size);
|
|
me->fileref->parent->fcb->inode_item.st_size += me->fileref->dc->utf8.Length * 2;
|
|
TRACE("me->fileref->parent->fcb->inode_item.st_size (inode %I64x) now %I64x\n", me->fileref->parent->fcb->inode, me->fileref->parent->fcb->inode_item.st_size);
|
|
me->fileref->parent->fcb->inode_item.transid = me->fileref->fcb->Vcb->superblock.generation;
|
|
me->fileref->parent->fcb->inode_item.sequence++;
|
|
me->fileref->parent->fcb->inode_item.st_ctime = now;
|
|
me->fileref->parent->fcb->inode_item.st_mtime = now;
|
|
me->fileref->parent->fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(me->fileref->parent->fcb);
|
|
} else {
|
|
if (me->fileref->dc) {
|
|
ExAcquireResourceExclusiveLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock, true);
|
|
RemoveEntryList(&me->fileref->dc->list_entry_index);
|
|
|
|
if (!me->fileref->fcb->ads)
|
|
remove_dir_child_from_hash_lists(me->fileref->parent->fcb, me->fileref->dc);
|
|
|
|
ExReleaseResourceLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock);
|
|
|
|
ExAcquireResourceExclusiveLite(&me->parent->fileref->fcb->nonpaged->dir_children_lock, true);
|
|
|
|
if (me->fileref->fcb->ads)
|
|
InsertHeadList(&me->parent->fileref->fcb->dir_children_index, &me->fileref->dc->list_entry_index);
|
|
else {
|
|
if (me->fileref->fcb->inode != SUBVOL_ROOT_INODE)
|
|
me->fileref->dc->key.obj_id = me->fileref->fcb->inode;
|
|
|
|
if (IsListEmpty(&me->parent->fileref->fcb->dir_children_index))
|
|
me->fileref->dc->index = 2;
|
|
else {
|
|
dir_child* dc2 = CONTAINING_RECORD(me->parent->fileref->fcb->dir_children_index.Blink, dir_child, list_entry_index);
|
|
|
|
me->fileref->dc->index = max(2, dc2->index + 1);
|
|
}
|
|
|
|
InsertTailList(&me->parent->fileref->fcb->dir_children_index, &me->fileref->dc->list_entry_index);
|
|
insert_dir_child_into_hash_lists(me->parent->fileref->fcb, me->fileref->dc);
|
|
}
|
|
|
|
ExReleaseResourceLite(&me->parent->fileref->fcb->nonpaged->dir_children_lock);
|
|
}
|
|
}
|
|
|
|
if (!me->dummyfileref->fcb->ads) {
|
|
Status = delete_fileref(me->dummyfileref, NULL, false, Irp, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_fileref returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
if (me->fileref->fcb->inode_item.st_nlink > 1) {
|
|
hl = ExAllocatePoolWithTag(PagedPool, sizeof(hardlink), ALLOC_TAG);
|
|
if (!hl) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
hl->parent = me->fileref->parent->fcb->inode;
|
|
hl->index = me->fileref->dc->index;
|
|
|
|
hl->utf8.Length = hl->utf8.MaximumLength = me->fileref->dc->utf8.Length;
|
|
hl->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, hl->utf8.MaximumLength, ALLOC_TAG);
|
|
if (!hl->utf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
ExFreePool(hl);
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(hl->utf8.Buffer, me->fileref->dc->utf8.Buffer, me->fileref->dc->utf8.Length);
|
|
|
|
hl->name.Length = hl->name.MaximumLength = me->fileref->dc->name.Length;
|
|
hl->name.Buffer = ExAllocatePoolWithTag(PagedPool, hl->name.MaximumLength, ALLOC_TAG);
|
|
if (!hl->name.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
ExFreePool(hl->utf8.Buffer);
|
|
ExFreePool(hl);
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(hl->name.Buffer, me->fileref->dc->name.Buffer, me->fileref->dc->name.Length);
|
|
|
|
InsertTailList(&me->fileref->fcb->hardlinks, &hl->list_entry);
|
|
}
|
|
|
|
mark_fileref_dirty(me->fileref);
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
// loop through, and only mark streams as deleted if their parent inodes are also deleted
|
|
|
|
le = move_list.Flink;
|
|
while (le != &move_list) {
|
|
me = CONTAINING_RECORD(le, move_entry, list_entry);
|
|
|
|
if (me->dummyfileref->fcb->ads && me->parent->dummyfileref->fcb->deleted) {
|
|
Status = delete_fileref(me->dummyfileref, NULL, false, Irp, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_fileref returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
destdir->fcb->subvol->root_item.ctransid = destdir->fcb->Vcb->superblock.generation;
|
|
destdir->fcb->subvol->root_item.ctime = now;
|
|
|
|
me = CONTAINING_RECORD(move_list.Flink, move_entry, list_entry);
|
|
send_notification_fileref(fileref, fileref->fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_ADDED, NULL);
|
|
send_notification_fileref(me->dummyfileref->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);
|
|
send_notification_fileref(fileref->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
end:
|
|
while (!IsListEmpty(&move_list)) {
|
|
le = RemoveHeadList(&move_list);
|
|
me = CONTAINING_RECORD(le, move_entry, list_entry);
|
|
|
|
if (me->dummyfcb)
|
|
free_fcb(me->dummyfcb);
|
|
|
|
if (me->dummyfileref)
|
|
free_fileref(me->dummyfileref);
|
|
|
|
free_fileref(me->fileref);
|
|
|
|
ExFreePool(me);
|
|
}
|
|
|
|
destdir->fcb->subvol->fcbs_version++;
|
|
fileref->fcb->subvol->fcbs_version++;
|
|
|
|
release_fcb_lock(fileref->fcb->Vcb);
|
|
|
|
return Status;
|
|
}
|
|
|
|
void insert_dir_child_into_hash_lists(fcb* fcb, dir_child* dc) {
|
|
bool inserted;
|
|
LIST_ENTRY* le;
|
|
uint8_t c, d;
|
|
|
|
c = dc->hash >> 24;
|
|
|
|
inserted = false;
|
|
|
|
d = c;
|
|
do {
|
|
le = fcb->hash_ptrs[d];
|
|
|
|
if (d == 0)
|
|
break;
|
|
|
|
d--;
|
|
} while (!le);
|
|
|
|
if (!le)
|
|
le = fcb->dir_children_hash.Flink;
|
|
|
|
while (le != &fcb->dir_children_hash) {
|
|
dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_hash);
|
|
|
|
if (dc2->hash > dc->hash) {
|
|
InsertHeadList(le->Blink, &dc->list_entry_hash);
|
|
inserted = true;
|
|
break;
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
if (!inserted)
|
|
InsertTailList(&fcb->dir_children_hash, &dc->list_entry_hash);
|
|
|
|
if (!fcb->hash_ptrs[c])
|
|
fcb->hash_ptrs[c] = &dc->list_entry_hash;
|
|
else {
|
|
dir_child* dc2 = CONTAINING_RECORD(fcb->hash_ptrs[c], dir_child, list_entry_hash);
|
|
|
|
if (dc2->hash > dc->hash)
|
|
fcb->hash_ptrs[c] = &dc->list_entry_hash;
|
|
}
|
|
|
|
c = dc->hash_uc >> 24;
|
|
|
|
inserted = false;
|
|
|
|
d = c;
|
|
do {
|
|
le = fcb->hash_ptrs_uc[d];
|
|
|
|
if (d == 0)
|
|
break;
|
|
|
|
d--;
|
|
} while (!le);
|
|
|
|
if (!le)
|
|
le = fcb->dir_children_hash_uc.Flink;
|
|
|
|
while (le != &fcb->dir_children_hash_uc) {
|
|
dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_hash_uc);
|
|
|
|
if (dc2->hash_uc > dc->hash_uc) {
|
|
InsertHeadList(le->Blink, &dc->list_entry_hash_uc);
|
|
inserted = true;
|
|
break;
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
if (!inserted)
|
|
InsertTailList(&fcb->dir_children_hash_uc, &dc->list_entry_hash_uc);
|
|
|
|
if (!fcb->hash_ptrs_uc[c])
|
|
fcb->hash_ptrs_uc[c] = &dc->list_entry_hash_uc;
|
|
else {
|
|
dir_child* dc2 = CONTAINING_RECORD(fcb->hash_ptrs_uc[c], dir_child, list_entry_hash_uc);
|
|
|
|
if (dc2->hash_uc > dc->hash_uc)
|
|
fcb->hash_ptrs_uc[c] = &dc->list_entry_hash_uc;
|
|
}
|
|
}
|
|
|
|
static NTSTATUS rename_stream_to_file(device_extension* Vcb, file_ref* fileref, ccb* ccb, ULONG flags,
|
|
PIRP Irp, LIST_ENTRY* rollback) {
|
|
NTSTATUS Status;
|
|
file_ref* ofr;
|
|
ANSI_STRING adsdata;
|
|
dir_child* dc;
|
|
fcb* dummyfcb;
|
|
|
|
if (fileref->fcb->type != BTRFS_TYPE_FILE)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
if (!(flags & FILE_RENAME_IGNORE_READONLY_ATTRIBUTE) && fileref->parent->fcb->atts & FILE_ATTRIBUTE_READONLY) {
|
|
WARN("trying to rename stream on readonly file\n");
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
if (Irp->RequestorMode == UserMode && ccb && !(ccb->access & DELETE)) {
|
|
WARN("insufficient permissions\n");
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
if (!(flags & FILE_RENAME_REPLACE_IF_EXISTS)) // file will always exist
|
|
return STATUS_OBJECT_NAME_COLLISION;
|
|
|
|
// FIXME - POSIX overwrites of stream?
|
|
|
|
ofr = fileref->parent;
|
|
|
|
if (ofr->open_count > 0) {
|
|
WARN("trying to overwrite open file\n");
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
if (ofr->fcb->inode_item.st_size > 0) {
|
|
WARN("can only overwrite existing stream if it is zero-length\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
dummyfcb = create_fcb(Vcb, PagedPool);
|
|
if (!dummyfcb) {
|
|
ERR("out of memory\n");
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
// copy parent fcb onto this one
|
|
|
|
fileref->fcb->subvol = ofr->fcb->subvol;
|
|
fileref->fcb->inode = ofr->fcb->inode;
|
|
fileref->fcb->hash = ofr->fcb->hash;
|
|
fileref->fcb->type = ofr->fcb->type;
|
|
fileref->fcb->inode_item = ofr->fcb->inode_item;
|
|
|
|
fileref->fcb->sd = ofr->fcb->sd;
|
|
ofr->fcb->sd = NULL;
|
|
|
|
fileref->fcb->deleted = ofr->fcb->deleted;
|
|
fileref->fcb->atts = ofr->fcb->atts;
|
|
|
|
fileref->fcb->reparse_xattr = ofr->fcb->reparse_xattr;
|
|
ofr->fcb->reparse_xattr.Buffer = NULL;
|
|
ofr->fcb->reparse_xattr.Length = ofr->fcb->reparse_xattr.MaximumLength = 0;
|
|
|
|
fileref->fcb->ea_xattr = ofr->fcb->ea_xattr;
|
|
ofr->fcb->ea_xattr.Buffer = NULL;
|
|
ofr->fcb->ea_xattr.Length = ofr->fcb->ea_xattr.MaximumLength = 0;
|
|
|
|
fileref->fcb->ealen = ofr->fcb->ealen;
|
|
|
|
while (!IsListEmpty(&ofr->fcb->hardlinks)) {
|
|
InsertTailList(&fileref->fcb->hardlinks, RemoveHeadList(&ofr->fcb->hardlinks));
|
|
}
|
|
|
|
fileref->fcb->inode_item_changed = true;
|
|
fileref->fcb->prop_compression = ofr->fcb->prop_compression;
|
|
|
|
while (!IsListEmpty(&ofr->fcb->xattrs)) {
|
|
InsertTailList(&fileref->fcb->xattrs, RemoveHeadList(&ofr->fcb->xattrs));
|
|
}
|
|
|
|
fileref->fcb->marked_as_orphan = ofr->fcb->marked_as_orphan;
|
|
fileref->fcb->case_sensitive = ofr->fcb->case_sensitive;
|
|
fileref->fcb->case_sensitive_set = ofr->fcb->case_sensitive_set;
|
|
|
|
while (!IsListEmpty(&ofr->fcb->dir_children_index)) {
|
|
InsertTailList(&fileref->fcb->dir_children_index, RemoveHeadList(&ofr->fcb->dir_children_index));
|
|
}
|
|
|
|
while (!IsListEmpty(&ofr->fcb->dir_children_hash)) {
|
|
InsertTailList(&fileref->fcb->dir_children_hash, RemoveHeadList(&ofr->fcb->dir_children_hash));
|
|
}
|
|
|
|
while (!IsListEmpty(&ofr->fcb->dir_children_hash_uc)) {
|
|
InsertTailList(&fileref->fcb->dir_children_hash_uc, RemoveHeadList(&ofr->fcb->dir_children_hash_uc));
|
|
}
|
|
|
|
fileref->fcb->hash_ptrs = ofr->fcb->hash_ptrs;
|
|
fileref->fcb->hash_ptrs_uc = ofr->fcb->hash_ptrs_uc;
|
|
|
|
ofr->fcb->hash_ptrs = NULL;
|
|
ofr->fcb->hash_ptrs_uc = NULL;
|
|
|
|
fileref->fcb->sd_dirty = ofr->fcb->sd_dirty;
|
|
fileref->fcb->sd_deleted = ofr->fcb->sd_deleted;
|
|
fileref->fcb->atts_changed = ofr->fcb->atts_changed;
|
|
fileref->fcb->atts_deleted = ofr->fcb->atts_deleted;
|
|
fileref->fcb->extents_changed = true;
|
|
fileref->fcb->reparse_xattr_changed = ofr->fcb->reparse_xattr_changed;
|
|
fileref->fcb->ea_changed = ofr->fcb->ea_changed;
|
|
fileref->fcb->prop_compression_changed = ofr->fcb->prop_compression_changed;
|
|
fileref->fcb->xattrs_changed = ofr->fcb->xattrs_changed;
|
|
fileref->fcb->created = ofr->fcb->created;
|
|
fileref->fcb->ads = false;
|
|
|
|
if (fileref->fcb->adsxattr.Buffer) {
|
|
ExFreePool(fileref->fcb->adsxattr.Buffer);
|
|
fileref->fcb->adsxattr.Length = fileref->fcb->adsxattr.MaximumLength = 0;
|
|
fileref->fcb->adsxattr.Buffer = NULL;
|
|
}
|
|
|
|
adsdata = fileref->fcb->adsdata;
|
|
|
|
fileref->fcb->adsdata.Buffer = NULL;
|
|
fileref->fcb->adsdata.Length = fileref->fcb->adsdata.MaximumLength = 0;
|
|
|
|
InsertHeadList(ofr->fcb->list_entry.Blink, &fileref->fcb->list_entry);
|
|
|
|
if (fileref->fcb->subvol->fcbs_ptrs[fileref->fcb->hash >> 24] == &ofr->fcb->list_entry)
|
|
fileref->fcb->subvol->fcbs_ptrs[fileref->fcb->hash >> 24] = &fileref->fcb->list_entry;
|
|
|
|
RemoveEntryList(&ofr->fcb->list_entry);
|
|
ofr->fcb->list_entry.Flink = ofr->fcb->list_entry.Blink = NULL;
|
|
|
|
mark_fcb_dirty(fileref->fcb);
|
|
|
|
// mark old parent fcb so it gets ignored by flush_fcb
|
|
ofr->fcb->created = true;
|
|
ofr->fcb->deleted = true;
|
|
|
|
mark_fcb_dirty(ofr->fcb);
|
|
|
|
// copy parent fileref onto this one
|
|
|
|
fileref->oldutf8 = ofr->oldutf8;
|
|
ofr->oldutf8.Buffer = NULL;
|
|
ofr->oldutf8.Length = ofr->oldutf8.MaximumLength = 0;
|
|
|
|
fileref->oldindex = ofr->oldindex;
|
|
fileref->delete_on_close = ofr->delete_on_close;
|
|
fileref->posix_delete = ofr->posix_delete;
|
|
fileref->deleted = ofr->deleted;
|
|
fileref->created = ofr->created;
|
|
|
|
fileref->parent = ofr->parent;
|
|
|
|
RemoveEntryList(&fileref->list_entry);
|
|
InsertHeadList(ofr->list_entry.Blink, &fileref->list_entry);
|
|
RemoveEntryList(&ofr->list_entry);
|
|
ofr->list_entry.Flink = ofr->list_entry.Blink = NULL;
|
|
|
|
while (!IsListEmpty(&ofr->children)) {
|
|
file_ref* fr = CONTAINING_RECORD(RemoveHeadList(&ofr->children), file_ref, list_entry);
|
|
|
|
free_fileref(fr->parent);
|
|
|
|
fr->parent = fileref;
|
|
InterlockedIncrement(&fileref->refcount);
|
|
|
|
InsertTailList(&fileref->children, &fr->list_entry);
|
|
}
|
|
|
|
dc = fileref->dc;
|
|
|
|
fileref->dc = ofr->dc;
|
|
fileref->dc->fileref = fileref;
|
|
|
|
mark_fileref_dirty(fileref);
|
|
|
|
// mark old parent fileref so it gets ignored by flush_fileref
|
|
ofr->created = true;
|
|
ofr->deleted = true;
|
|
|
|
// write file data
|
|
|
|
fileref->fcb->inode_item.st_size = adsdata.Length;
|
|
|
|
if (adsdata.Length > 0) {
|
|
bool make_inline = adsdata.Length <= Vcb->options.max_inline;
|
|
|
|
if (make_inline) {
|
|
EXTENT_DATA* ed = ExAllocatePoolWithTag(PagedPool, (uint16_t)(offsetof(EXTENT_DATA, data[0]) + adsdata.Length), ALLOC_TAG);
|
|
if (!ed) {
|
|
ERR("out of memory\n");
|
|
ExFreePool(adsdata.Buffer);
|
|
reap_fcb(dummyfcb);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
ed->generation = Vcb->superblock.generation;
|
|
ed->decoded_size = adsdata.Length;
|
|
ed->compression = BTRFS_COMPRESSION_NONE;
|
|
ed->encryption = BTRFS_ENCRYPTION_NONE;
|
|
ed->encoding = BTRFS_ENCODING_NONE;
|
|
ed->type = EXTENT_TYPE_INLINE;
|
|
|
|
RtlCopyMemory(ed->data, adsdata.Buffer, adsdata.Length);
|
|
|
|
ExFreePool(adsdata.Buffer);
|
|
|
|
Status = add_extent_to_fcb(fileref->fcb, 0, ed, (uint16_t)(offsetof(EXTENT_DATA, data[0]) + adsdata.Length), false, NULL, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("add_extent_to_fcb returned %08x\n", Status);
|
|
ExFreePool(ed);
|
|
reap_fcb(dummyfcb);
|
|
return Status;
|
|
}
|
|
|
|
ExFreePool(ed);
|
|
} else if (adsdata.Length % Vcb->superblock.sector_size) {
|
|
char* newbuf = ExAllocatePoolWithTag(PagedPool, (uint16_t)sector_align(adsdata.Length, Vcb->superblock.sector_size), ALLOC_TAG);
|
|
if (!newbuf) {
|
|
ERR("out of memory\n");
|
|
ExFreePool(adsdata.Buffer);
|
|
reap_fcb(dummyfcb);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(newbuf, adsdata.Buffer, adsdata.Length);
|
|
RtlZeroMemory(newbuf + adsdata.Length, (uint16_t)(sector_align(adsdata.Length, Vcb->superblock.sector_size) - adsdata.Length));
|
|
|
|
ExFreePool(adsdata.Buffer);
|
|
|
|
adsdata.Buffer = newbuf;
|
|
adsdata.Length = adsdata.MaximumLength = (uint16_t)sector_align(adsdata.Length, Vcb->superblock.sector_size);
|
|
}
|
|
|
|
if (!make_inline) {
|
|
Status = do_write_file(fileref->fcb, 0, adsdata.Length, adsdata.Buffer, Irp, false, 0, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("do_write_file returned %08x\n", Status);
|
|
ExFreePool(adsdata.Buffer);
|
|
reap_fcb(dummyfcb);
|
|
return Status;
|
|
}
|
|
|
|
ExFreePool(adsdata.Buffer);
|
|
}
|
|
|
|
fileref->fcb->inode_item.st_blocks = adsdata.Length;
|
|
fileref->fcb->inode_item_changed = true;
|
|
}
|
|
|
|
RemoveEntryList(&dc->list_entry_index);
|
|
|
|
if (dc->utf8.Buffer)
|
|
ExFreePool(dc->utf8.Buffer);
|
|
|
|
if (dc->name.Buffer)
|
|
ExFreePool(dc->name.Buffer);
|
|
|
|
if (dc->name_uc.Buffer)
|
|
ExFreePool(dc->name_uc.Buffer);
|
|
|
|
ExFreePool(dc);
|
|
|
|
// FIXME - csums?
|
|
|
|
// add dummy deleted xattr with old name
|
|
|
|
dummyfcb->Vcb = Vcb;
|
|
dummyfcb->subvol = fileref->fcb->subvol;
|
|
dummyfcb->inode = fileref->fcb->inode;
|
|
dummyfcb->adsxattr = fileref->fcb->adsxattr;
|
|
dummyfcb->adshash = fileref->fcb->adshash;
|
|
dummyfcb->ads = true;
|
|
dummyfcb->deleted = true;
|
|
|
|
// FIXME - dummyfileref as well?
|
|
|
|
mark_fcb_dirty(dummyfcb);
|
|
|
|
free_fcb(dummyfcb);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS rename_stream(device_extension* Vcb, file_ref* fileref, ccb* ccb, FILE_RENAME_INFORMATION_EX* fri,
|
|
ULONG flags, PIRP Irp, LIST_ENTRY* rollback) {
|
|
NTSTATUS Status;
|
|
UNICODE_STRING fn;
|
|
file_ref* sf = NULL;
|
|
uint16_t newmaxlen;
|
|
ULONG utf8len;
|
|
ANSI_STRING utf8;
|
|
UNICODE_STRING utf16, utf16uc;
|
|
ANSI_STRING adsxattr;
|
|
uint32_t crc32;
|
|
fcb* dummyfcb;
|
|
|
|
static const WCHAR datasuf[] = L":$DATA";
|
|
static const char xapref[] = "user.";
|
|
|
|
if (!fileref) {
|
|
ERR("fileref not set\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (!fileref->parent) {
|
|
ERR("fileref->parent not set\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (fri->FileNameLength < sizeof(WCHAR)) {
|
|
WARN("filename too short\n");
|
|
return STATUS_OBJECT_NAME_INVALID;
|
|
}
|
|
|
|
if (fri->FileName[0] != ':') {
|
|
WARN("destination filename must begin with a colon\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (Irp->RequestorMode == UserMode && ccb && !(ccb->access & DELETE)) {
|
|
WARN("insufficient permissions\n");
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
fn.Buffer = &fri->FileName[1];
|
|
fn.Length = fn.MaximumLength = (USHORT)(fri->FileNameLength - sizeof(WCHAR));
|
|
|
|
// remove :$DATA suffix
|
|
if (fn.Length >= sizeof(datasuf) - sizeof(WCHAR) &&
|
|
RtlCompareMemory(&fn.Buffer[(fn.Length - sizeof(datasuf) + sizeof(WCHAR))/sizeof(WCHAR)], datasuf, sizeof(datasuf) - sizeof(WCHAR)) == sizeof(datasuf) - sizeof(WCHAR))
|
|
fn.Length -= sizeof(datasuf) - sizeof(WCHAR);
|
|
|
|
if (fn.Length == 0)
|
|
return rename_stream_to_file(Vcb, fileref, ccb, flags, Irp, rollback);
|
|
|
|
if (!is_file_name_valid(&fn, false, true)) {
|
|
WARN("invalid stream name %.*S\n", fn.Length / sizeof(WCHAR), fn.Buffer);
|
|
return STATUS_OBJECT_NAME_INVALID;
|
|
}
|
|
|
|
if (!(flags & FILE_RENAME_IGNORE_READONLY_ATTRIBUTE) && fileref->parent->fcb->atts & FILE_ATTRIBUTE_READONLY) {
|
|
WARN("trying to rename stream on readonly file\n");
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
Status = open_fileref_child(Vcb, fileref->parent, &fn, fileref->parent->fcb->case_sensitive, true, true, PagedPool, &sf, Irp);
|
|
if (Status != STATUS_OBJECT_NAME_NOT_FOUND) {
|
|
if (Status == STATUS_SUCCESS) {
|
|
if (fileref == sf || sf->deleted) {
|
|
free_fileref(sf);
|
|
sf = NULL;
|
|
} else {
|
|
if (!(flags & FILE_RENAME_REPLACE_IF_EXISTS)) {
|
|
Status = STATUS_OBJECT_NAME_COLLISION;
|
|
goto end;
|
|
}
|
|
|
|
// FIXME - POSIX overwrites of stream?
|
|
|
|
if (sf->open_count > 0) {
|
|
WARN("trying to overwrite open file\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
|
|
if (sf->fcb->adsdata.Length > 0) {
|
|
WARN("can only overwrite existing stream if it is zero-length\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
Status = delete_fileref(sf, NULL, false, Irp, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_fileref returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
}
|
|
} else {
|
|
ERR("open_fileref_child returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
Status = utf16_to_utf8(NULL, 0, &utf8len, fn.Buffer, fn.Length);
|
|
if (!NT_SUCCESS(Status))
|
|
goto end;
|
|
|
|
utf8.MaximumLength = utf8.Length = (uint16_t)utf8len;
|
|
utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.MaximumLength, ALLOC_TAG);
|
|
if (!utf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
Status = utf16_to_utf8(utf8.Buffer, utf8len, &utf8len, fn.Buffer, fn.Length);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ExFreePool(utf8.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
adsxattr.Length = adsxattr.MaximumLength = sizeof(xapref) - 1 + utf8.Length;
|
|
adsxattr.Buffer = ExAllocatePoolWithTag(PagedPool, adsxattr.MaximumLength, ALLOC_TAG);
|
|
if (!adsxattr.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
ExFreePool(utf8.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(adsxattr.Buffer, xapref, sizeof(xapref) - 1);
|
|
RtlCopyMemory(&adsxattr.Buffer[sizeof(xapref) - 1], utf8.Buffer, utf8.Length);
|
|
|
|
// don't allow if it's one of our reserved names
|
|
|
|
if ((adsxattr.Length == sizeof(EA_DOSATTRIB) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_DOSATTRIB, adsxattr.Length) == adsxattr.Length) ||
|
|
(adsxattr.Length == sizeof(EA_EA) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_EA, adsxattr.Length) == adsxattr.Length) ||
|
|
(adsxattr.Length == sizeof(EA_REPARSE) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_REPARSE, adsxattr.Length) == adsxattr.Length) ||
|
|
(adsxattr.Length == sizeof(EA_CASE_SENSITIVE) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_CASE_SENSITIVE, adsxattr.Length) == adsxattr.Length)) {
|
|
Status = STATUS_OBJECT_NAME_INVALID;
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
utf16.Length = utf16.MaximumLength = fn.Length;
|
|
utf16.Buffer = ExAllocatePoolWithTag(PagedPool, utf16.MaximumLength, ALLOC_TAG);
|
|
if (!utf16.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(utf16.Buffer, fn.Buffer, fn.Length);
|
|
|
|
newmaxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node) -
|
|
offsetof(DIR_ITEM, name[0]);
|
|
|
|
if (newmaxlen < adsxattr.Length) {
|
|
WARN("cannot rename as data too long\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(utf16.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
newmaxlen -= adsxattr.Length;
|
|
|
|
if (newmaxlen < fileref->fcb->adsdata.Length) {
|
|
WARN("cannot rename as data too long\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(utf16.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
Status = RtlUpcaseUnicodeString(&utf16uc, &fn, true);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("RtlUpcaseUnicodeString returned %08x\n");
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(utf16.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
// add dummy deleted xattr with old name
|
|
|
|
dummyfcb = create_fcb(Vcb, PagedPool);
|
|
if (!dummyfcb) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(utf16.Buffer);
|
|
ExFreePool(utf16uc.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
dummyfcb->Vcb = Vcb;
|
|
dummyfcb->subvol = fileref->fcb->subvol;
|
|
dummyfcb->inode = fileref->fcb->inode;
|
|
dummyfcb->adsxattr = fileref->fcb->adsxattr;
|
|
dummyfcb->adshash = fileref->fcb->adshash;
|
|
dummyfcb->ads = true;
|
|
dummyfcb->deleted = true;
|
|
|
|
mark_fcb_dirty(dummyfcb);
|
|
|
|
free_fcb(dummyfcb);
|
|
|
|
// change fcb values
|
|
|
|
fileref->dc->utf8 = utf8;
|
|
fileref->dc->name = utf16;
|
|
fileref->dc->name_uc = utf16uc;
|
|
|
|
crc32 = calc_crc32c(0xfffffffe, (uint8_t*)adsxattr.Buffer, adsxattr.Length);
|
|
|
|
fileref->fcb->adsxattr = adsxattr;
|
|
fileref->fcb->adshash = crc32;
|
|
fileref->fcb->adsmaxlen = newmaxlen;
|
|
|
|
fileref->fcb->created = true;
|
|
|
|
mark_fcb_dirty(fileref->fcb);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
end:
|
|
if (sf)
|
|
free_fileref(sf);
|
|
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS rename_file_to_stream(device_extension* Vcb, file_ref* fileref, ccb* ccb, FILE_RENAME_INFORMATION_EX* fri,
|
|
ULONG flags, PIRP Irp, LIST_ENTRY* rollback) {
|
|
NTSTATUS Status;
|
|
UNICODE_STRING fn;
|
|
file_ref* sf = NULL;
|
|
uint16_t newmaxlen;
|
|
ULONG utf8len;
|
|
ANSI_STRING utf8;
|
|
UNICODE_STRING utf16, utf16uc;
|
|
ANSI_STRING adsxattr, adsdata;
|
|
uint32_t crc32;
|
|
fcb* dummyfcb;
|
|
file_ref* dummyfileref;
|
|
dir_child* dc;
|
|
LIST_ENTRY* le;
|
|
|
|
static const WCHAR datasuf[] = L":$DATA";
|
|
static const char xapref[] = "user.";
|
|
|
|
if (!fileref) {
|
|
ERR("fileref not set\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (fri->FileNameLength < sizeof(WCHAR)) {
|
|
WARN("filename too short\n");
|
|
return STATUS_OBJECT_NAME_INVALID;
|
|
}
|
|
|
|
if (fri->FileName[0] != ':') {
|
|
WARN("destination filename must begin with a colon\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (Irp->RequestorMode == UserMode && ccb && !(ccb->access & DELETE)) {
|
|
WARN("insufficient permissions\n");
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
if (fileref->fcb->type != BTRFS_TYPE_FILE)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
fn.Buffer = &fri->FileName[1];
|
|
fn.Length = fn.MaximumLength = (USHORT)(fri->FileNameLength - sizeof(WCHAR));
|
|
|
|
// remove :$DATA suffix
|
|
if (fn.Length >= sizeof(datasuf) - sizeof(WCHAR) &&
|
|
RtlCompareMemory(&fn.Buffer[(fn.Length - sizeof(datasuf) + sizeof(WCHAR))/sizeof(WCHAR)], datasuf, sizeof(datasuf) - sizeof(WCHAR)) == sizeof(datasuf) - sizeof(WCHAR))
|
|
fn.Length -= sizeof(datasuf) - sizeof(WCHAR);
|
|
|
|
if (fn.Length == 0) {
|
|
WARN("not allowing overwriting file with itself\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (!is_file_name_valid(&fn, false, true)) {
|
|
WARN("invalid stream name %.*S\n", fn.Length / sizeof(WCHAR), fn.Buffer);
|
|
return STATUS_OBJECT_NAME_INVALID;
|
|
}
|
|
|
|
if (!(flags & FILE_RENAME_IGNORE_READONLY_ATTRIBUTE) && fileref->fcb->atts & FILE_ATTRIBUTE_READONLY) {
|
|
WARN("trying to rename stream on readonly file\n");
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
Status = open_fileref_child(Vcb, fileref, &fn, fileref->fcb->case_sensitive, true, true, PagedPool, &sf, Irp);
|
|
if (Status != STATUS_OBJECT_NAME_NOT_FOUND) {
|
|
if (Status == STATUS_SUCCESS) {
|
|
if (fileref == sf || sf->deleted) {
|
|
free_fileref(sf);
|
|
sf = NULL;
|
|
} else {
|
|
if (!(flags & FILE_RENAME_REPLACE_IF_EXISTS)) {
|
|
Status = STATUS_OBJECT_NAME_COLLISION;
|
|
goto end;
|
|
}
|
|
|
|
// FIXME - POSIX overwrites of stream?
|
|
|
|
if (sf->open_count > 0) {
|
|
WARN("trying to overwrite open file\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
|
|
if (sf->fcb->adsdata.Length > 0) {
|
|
WARN("can only overwrite existing stream if it is zero-length\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
Status = delete_fileref(sf, NULL, false, Irp, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_fileref returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
}
|
|
} else {
|
|
ERR("open_fileref_child returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
Status = utf16_to_utf8(NULL, 0, &utf8len, fn.Buffer, fn.Length);
|
|
if (!NT_SUCCESS(Status))
|
|
goto end;
|
|
|
|
utf8.MaximumLength = utf8.Length = (uint16_t)utf8len;
|
|
utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.MaximumLength, ALLOC_TAG);
|
|
if (!utf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
Status = utf16_to_utf8(utf8.Buffer, utf8len, &utf8len, fn.Buffer, fn.Length);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ExFreePool(utf8.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
adsxattr.Length = adsxattr.MaximumLength = sizeof(xapref) - 1 + utf8.Length;
|
|
adsxattr.Buffer = ExAllocatePoolWithTag(PagedPool, adsxattr.MaximumLength, ALLOC_TAG);
|
|
if (!adsxattr.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
ExFreePool(utf8.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(adsxattr.Buffer, xapref, sizeof(xapref) - 1);
|
|
RtlCopyMemory(&adsxattr.Buffer[sizeof(xapref) - 1], utf8.Buffer, utf8.Length);
|
|
|
|
// don't allow if it's one of our reserved names
|
|
|
|
if ((adsxattr.Length == sizeof(EA_DOSATTRIB) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_DOSATTRIB, adsxattr.Length) == adsxattr.Length) ||
|
|
(adsxattr.Length == sizeof(EA_EA) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_EA, adsxattr.Length) == adsxattr.Length) ||
|
|
(adsxattr.Length == sizeof(EA_REPARSE) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_REPARSE, adsxattr.Length) == adsxattr.Length) ||
|
|
(adsxattr.Length == sizeof(EA_CASE_SENSITIVE) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_CASE_SENSITIVE, adsxattr.Length) == adsxattr.Length)) {
|
|
Status = STATUS_OBJECT_NAME_INVALID;
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
utf16.Length = utf16.MaximumLength = fn.Length;
|
|
utf16.Buffer = ExAllocatePoolWithTag(PagedPool, utf16.MaximumLength, ALLOC_TAG);
|
|
if (!utf16.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(utf16.Buffer, fn.Buffer, fn.Length);
|
|
|
|
newmaxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node) -
|
|
offsetof(DIR_ITEM, name[0]);
|
|
|
|
if (newmaxlen < adsxattr.Length) {
|
|
WARN("cannot rename as data too long\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(utf16.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
newmaxlen -= adsxattr.Length;
|
|
|
|
if (newmaxlen < fileref->fcb->inode_item.st_size) {
|
|
WARN("cannot rename as data too long\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(utf16.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
Status = RtlUpcaseUnicodeString(&utf16uc, &fn, true);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("RtlUpcaseUnicodeString returned %08x\n");
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(utf16.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
// read existing file data
|
|
|
|
if (fileref->fcb->inode_item.st_size > 0) {
|
|
ULONG bytes_read;
|
|
|
|
adsdata.Length = adsdata.MaximumLength = (uint16_t)fileref->fcb->inode_item.st_size;
|
|
|
|
adsdata.Buffer = ExAllocatePoolWithTag(PagedPool, adsdata.MaximumLength, ALLOC_TAG);
|
|
if (!adsdata.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(utf16.Buffer);
|
|
ExFreePool(utf16uc.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
Status = read_file(fileref->fcb, (uint8_t*)adsdata.Buffer, 0, adsdata.Length, &bytes_read, Irp);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(utf16.Buffer);
|
|
ExFreePool(utf16uc.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
ExFreePool(adsdata.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
if (bytes_read < fileref->fcb->inode_item.st_size) {
|
|
ERR("short read\n");
|
|
Status = STATUS_INTERNAL_ERROR;
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(utf16.Buffer);
|
|
ExFreePool(utf16uc.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
ExFreePool(adsdata.Buffer);
|
|
goto end;
|
|
}
|
|
} else
|
|
adsdata.Buffer = NULL;
|
|
|
|
dc = ExAllocatePoolWithTag(PagedPool, sizeof(dir_child), ALLOC_TAG);
|
|
if (!dc) {
|
|
ERR("short read\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;;
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(utf16.Buffer);
|
|
ExFreePool(utf16uc.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
|
|
if (adsdata.Buffer)
|
|
ExFreePool(adsdata.Buffer);
|
|
|
|
goto end;
|
|
}
|
|
|
|
// add dummy deleted fcb with old name
|
|
|
|
Status = duplicate_fcb(fileref->fcb, &dummyfcb);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("duplicate_fcb returned %08x\n", Status);
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(utf16.Buffer);
|
|
ExFreePool(utf16uc.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
|
|
if (adsdata.Buffer)
|
|
ExFreePool(adsdata.Buffer);
|
|
|
|
ExFreePool(dc);
|
|
|
|
goto end;
|
|
}
|
|
|
|
dummyfileref = create_fileref(Vcb);
|
|
if (!dummyfileref) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(utf16.Buffer);
|
|
ExFreePool(utf16uc.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
|
|
if (adsdata.Buffer)
|
|
ExFreePool(adsdata.Buffer);
|
|
|
|
ExFreePool(dc);
|
|
|
|
reap_fcb(dummyfcb);
|
|
|
|
goto end;
|
|
}
|
|
|
|
dummyfileref->fcb = dummyfcb;
|
|
|
|
dummyfcb->Vcb = Vcb;
|
|
dummyfcb->subvol = fileref->fcb->subvol;
|
|
dummyfcb->inode = fileref->fcb->inode;
|
|
dummyfcb->hash = fileref->fcb->hash;
|
|
|
|
if (fileref->fcb->inode_item.st_size > 0) {
|
|
Status = excise_extents(Vcb, dummyfcb, 0, sector_align(fileref->fcb->inode_item.st_size, Vcb->superblock.sector_size),
|
|
Irp, rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("excise_extents returned %08x\n", Status);
|
|
ExFreePool(utf8.Buffer);
|
|
ExFreePool(utf16.Buffer);
|
|
ExFreePool(utf16uc.Buffer);
|
|
ExFreePool(adsxattr.Buffer);
|
|
ExFreePool(adsdata.Buffer);
|
|
ExFreePool(dc);
|
|
|
|
reap_fileref(Vcb, dummyfileref);
|
|
reap_fcb(dummyfcb);
|
|
|
|
goto end;
|
|
}
|
|
|
|
dummyfcb->inode_item.st_size = 0;
|
|
dummyfcb->Header.AllocationSize.QuadPart = 0;
|
|
dummyfcb->Header.FileSize.QuadPart = 0;
|
|
dummyfcb->Header.ValidDataLength.QuadPart = 0;
|
|
}
|
|
|
|
dummyfcb->hash_ptrs = fileref->fcb->hash_ptrs;
|
|
dummyfcb->hash_ptrs_uc = fileref->fcb->hash_ptrs_uc;
|
|
dummyfcb->created = fileref->fcb->created;
|
|
|
|
le = fileref->fcb->extents.Flink;
|
|
while (le != &fileref->fcb->extents) {
|
|
extent* ext = CONTAINING_RECORD(le, extent, list_entry);
|
|
|
|
ext->ignore = true;
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
while (!IsListEmpty(&fileref->fcb->dir_children_index)) {
|
|
InsertTailList(&dummyfcb->dir_children_index, RemoveHeadList(&fileref->fcb->dir_children_index));
|
|
}
|
|
|
|
while (!IsListEmpty(&fileref->fcb->dir_children_hash)) {
|
|
InsertTailList(&dummyfcb->dir_children_hash, RemoveHeadList(&fileref->fcb->dir_children_hash));
|
|
}
|
|
|
|
while (!IsListEmpty(&fileref->fcb->dir_children_hash_uc)) {
|
|
InsertTailList(&dummyfcb->dir_children_hash_uc, RemoveHeadList(&fileref->fcb->dir_children_hash_uc));
|
|
}
|
|
|
|
InsertTailList(&Vcb->all_fcbs, &dummyfcb->list_entry_all);
|
|
|
|
InsertHeadList(fileref->fcb->list_entry.Blink, &dummyfcb->list_entry);
|
|
|
|
if (fileref->fcb->subvol->fcbs_ptrs[dummyfcb->hash >> 24] == &fileref->fcb->list_entry)
|
|
fileref->fcb->subvol->fcbs_ptrs[dummyfcb->hash >> 24] = &dummyfcb->list_entry;
|
|
|
|
RemoveEntryList(&fileref->fcb->list_entry);
|
|
fileref->fcb->list_entry.Flink = fileref->fcb->list_entry.Blink = NULL;
|
|
|
|
mark_fcb_dirty(dummyfcb);
|
|
|
|
// create dummy fileref
|
|
|
|
dummyfileref->oldutf8 = fileref->oldutf8;
|
|
dummyfileref->oldindex = fileref->oldindex;
|
|
dummyfileref->delete_on_close = fileref->delete_on_close;
|
|
dummyfileref->posix_delete = fileref->posix_delete;
|
|
dummyfileref->deleted = fileref->deleted;
|
|
dummyfileref->created = fileref->created;
|
|
dummyfileref->parent = fileref->parent;
|
|
|
|
while (!IsListEmpty(&fileref->children)) {
|
|
file_ref* fr = CONTAINING_RECORD(RemoveHeadList(&fileref->children), file_ref, list_entry);
|
|
|
|
free_fileref(fr->parent);
|
|
|
|
fr->parent = dummyfileref;
|
|
InterlockedIncrement(&dummyfileref->refcount);
|
|
|
|
InsertTailList(&dummyfileref->children, &fr->list_entry);
|
|
}
|
|
|
|
InsertTailList(fileref->list_entry.Blink, &dummyfileref->list_entry);
|
|
|
|
RemoveEntryList(&fileref->list_entry);
|
|
InsertTailList(&dummyfileref->children, &fileref->list_entry);
|
|
|
|
dummyfileref->dc = fileref->dc;
|
|
dummyfileref->dc->fileref = dummyfileref;
|
|
|
|
mark_fileref_dirty(dummyfileref);
|
|
|
|
free_fileref(dummyfileref);
|
|
|
|
// change fcb values
|
|
|
|
fileref->fcb->hash_ptrs = NULL;
|
|
fileref->fcb->hash_ptrs_uc = NULL;
|
|
|
|
fileref->fcb->ads = true;
|
|
|
|
fileref->oldutf8.Length = fileref->oldutf8.MaximumLength = 0;
|
|
fileref->oldutf8.Buffer = NULL;
|
|
|
|
RtlZeroMemory(dc, sizeof(dir_child));
|
|
|
|
dc->utf8 = utf8;
|
|
dc->name = utf16;
|
|
dc->hash = calc_crc32c(0xffffffff, (uint8_t*)dc->name.Buffer, dc->name.Length);
|
|
dc->name_uc = utf16uc;
|
|
dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)dc->name_uc.Buffer, dc->name_uc.Length);
|
|
dc->fileref = fileref;
|
|
InsertTailList(&dummyfcb->dir_children_index, &dc->list_entry_index);
|
|
|
|
fileref->dc = dc;
|
|
fileref->parent = dummyfileref;
|
|
|
|
crc32 = calc_crc32c(0xfffffffe, (uint8_t*)adsxattr.Buffer, adsxattr.Length);
|
|
|
|
fileref->fcb->adsxattr = adsxattr;
|
|
fileref->fcb->adshash = crc32;
|
|
fileref->fcb->adsmaxlen = newmaxlen;
|
|
fileref->fcb->adsdata = adsdata;
|
|
|
|
fileref->fcb->created = true;
|
|
|
|
mark_fcb_dirty(fileref->fcb);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
end:
|
|
if (sf)
|
|
free_fileref(sf);
|
|
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS set_rename_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, PFILE_OBJECT tfo, bool ex) {
|
|
FILE_RENAME_INFORMATION_EX* fri = Irp->AssociatedIrp.SystemBuffer;
|
|
fcb* fcb = FileObject->FsContext;
|
|
ccb* ccb = FileObject->FsContext2;
|
|
file_ref *fileref = ccb ? ccb->fileref : NULL, *oldfileref = NULL, *related = NULL, *fr2 = NULL;
|
|
WCHAR* fn;
|
|
ULONG fnlen, utf8len, origutf8len;
|
|
UNICODE_STRING fnus;
|
|
ANSI_STRING utf8;
|
|
NTSTATUS Status;
|
|
LARGE_INTEGER time;
|
|
BTRFS_TIME now;
|
|
LIST_ENTRY rollback, *le;
|
|
hardlink* hl;
|
|
SECURITY_SUBJECT_CONTEXT subjcont;
|
|
ACCESS_MASK access;
|
|
ULONG flags;
|
|
#ifdef __REACTOS__
|
|
unsigned int i;
|
|
#endif
|
|
|
|
InitializeListHead(&rollback);
|
|
|
|
if (ex)
|
|
flags = fri->Flags;
|
|
else
|
|
flags = fri->ReplaceIfExists ? FILE_RENAME_REPLACE_IF_EXISTS : 0;
|
|
|
|
TRACE("tfo = %p\n", tfo);
|
|
TRACE("Flags = %x\n", flags);
|
|
TRACE("RootDirectory = %p\n", fri->RootDirectory);
|
|
TRACE("FileName = %.*S\n", fri->FileNameLength / sizeof(WCHAR), fri->FileName);
|
|
|
|
fn = fri->FileName;
|
|
fnlen = fri->FileNameLength / sizeof(WCHAR);
|
|
|
|
if (!tfo) {
|
|
if (!fileref || !fileref->parent) {
|
|
ERR("no fileref set and no directory given\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
} else {
|
|
LONG i;
|
|
|
|
while (fnlen > 0 && (fri->FileName[fnlen - 1] == '/' || fri->FileName[fnlen - 1] == '\\'))
|
|
fnlen--;
|
|
|
|
if (fnlen == 0)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
for (i = fnlen - 1; i >= 0; i--) {
|
|
if (fri->FileName[i] == '\\' || fri->FileName[i] == '/') {
|
|
fn = &fri->FileName[i+1];
|
|
fnlen = (fri->FileNameLength / sizeof(WCHAR)) - i - 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ExAcquireResourceSharedLite(&Vcb->tree_lock, true);
|
|
ExAcquireResourceExclusiveLite(&Vcb->fileref_lock, true);
|
|
ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);
|
|
|
|
if (fcb->inode == SUBVOL_ROOT_INODE && fcb->subvol->id == BTRFS_ROOT_FSTREE) {
|
|
WARN("not allowing \\$Root to be renamed\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
|
|
if (fcb->ads) {
|
|
if (FileObject->SectionObjectPointer && FileObject->SectionObjectPointer->DataSectionObject) {
|
|
IO_STATUS_BLOCK iosb;
|
|
|
|
CcFlushCache(FileObject->SectionObjectPointer, NULL, 0, &iosb);
|
|
if (!NT_SUCCESS(iosb.Status)) {
|
|
ERR("CcFlushCache returned %08x\n", iosb.Status);
|
|
Status = iosb.Status;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
Status = rename_stream(Vcb, fileref, ccb, fri, flags, Irp, &rollback);
|
|
goto end;
|
|
} else if (fnlen >= 1 && fn[0] == ':') {
|
|
if (FileObject->SectionObjectPointer && FileObject->SectionObjectPointer->DataSectionObject) {
|
|
IO_STATUS_BLOCK iosb;
|
|
|
|
CcFlushCache(FileObject->SectionObjectPointer, NULL, 0, &iosb);
|
|
if (!NT_SUCCESS(iosb.Status)) {
|
|
ERR("CcFlushCache returned %08x\n", iosb.Status);
|
|
Status = iosb.Status;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
Status = rename_file_to_stream(Vcb, fileref, ccb, fri, flags, Irp, &rollback);
|
|
goto end;
|
|
}
|
|
|
|
fnus.Buffer = fn;
|
|
fnus.Length = fnus.MaximumLength = (uint16_t)(fnlen * sizeof(WCHAR));
|
|
|
|
TRACE("fnus = %.*S\n", fnus.Length / sizeof(WCHAR), fnus.Buffer);
|
|
|
|
#ifndef __REACTOS__
|
|
for (unsigned int i = 0 ; i < fnus.Length / sizeof(WCHAR); i++) {
|
|
#else
|
|
for (i = 0 ; i < fnus.Length / sizeof(WCHAR); i++) {
|
|
#endif
|
|
if (fnus.Buffer[i] == ':') {
|
|
TRACE("colon in filename\n");
|
|
Status = STATUS_OBJECT_NAME_INVALID;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
origutf8len = fileref->dc->utf8.Length;
|
|
|
|
Status = utf16_to_utf8(NULL, 0, &utf8len, fn, (ULONG)fnlen * sizeof(WCHAR));
|
|
if (!NT_SUCCESS(Status))
|
|
goto end;
|
|
|
|
utf8.MaximumLength = utf8.Length = (uint16_t)utf8len;
|
|
utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.MaximumLength, ALLOC_TAG);
|
|
if (!utf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
Status = utf16_to_utf8(utf8.Buffer, utf8len, &utf8len, fn, (ULONG)fnlen * sizeof(WCHAR));
|
|
if (!NT_SUCCESS(Status))
|
|
goto end;
|
|
|
|
if (tfo && tfo->FsContext2) {
|
|
struct _ccb* relatedccb = tfo->FsContext2;
|
|
|
|
related = relatedccb->fileref;
|
|
increase_fileref_refcount(related);
|
|
} else if (fnus.Length >= sizeof(WCHAR) && fnus.Buffer[0] != '\\') {
|
|
related = fileref->parent;
|
|
increase_fileref_refcount(related);
|
|
}
|
|
|
|
Status = open_fileref(Vcb, &oldfileref, &fnus, related, false, NULL, NULL, PagedPool, ccb->case_sensitive, Irp);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
TRACE("destination file already exists\n");
|
|
|
|
if (fileref != oldfileref && !oldfileref->deleted) {
|
|
if (!(flags & FILE_RENAME_REPLACE_IF_EXISTS)) {
|
|
Status = STATUS_OBJECT_NAME_COLLISION;
|
|
goto end;
|
|
} else if (fileref == oldfileref) {
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
} else if (!(flags & FILE_RENAME_POSIX_SEMANTICS) && (oldfileref->open_count > 0 || has_open_children(oldfileref)) && !oldfileref->deleted) {
|
|
WARN("trying to overwrite open file\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
} else if (!(flags & FILE_RENAME_IGNORE_READONLY_ATTRIBUTE) && oldfileref->fcb->atts & FILE_ATTRIBUTE_READONLY) {
|
|
WARN("trying to overwrite readonly file\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
} else if (oldfileref->fcb->type == BTRFS_TYPE_DIRECTORY) {
|
|
WARN("trying to overwrite directory\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
if (fileref == oldfileref || oldfileref->deleted) {
|
|
free_fileref(oldfileref);
|
|
oldfileref = NULL;
|
|
}
|
|
}
|
|
|
|
if (!related) {
|
|
Status = open_fileref(Vcb, &related, &fnus, NULL, true, NULL, NULL, PagedPool, ccb->case_sensitive, Irp);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("open_fileref returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
if (related->fcb == Vcb->dummy_fcb) {
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
|
|
SeCaptureSubjectContext(&subjcont);
|
|
|
|
if (!SeAccessCheck(related->fcb->sd, &subjcont, false, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_ADD_SUBDIRECTORY : FILE_ADD_FILE, 0, NULL,
|
|
IoGetFileObjectGenericMapping(), Irp->RequestorMode, &access, &Status)) {
|
|
SeReleaseSubjectContext(&subjcont);
|
|
TRACE("SeAccessCheck failed, returning %08x\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
SeReleaseSubjectContext(&subjcont);
|
|
|
|
if (has_open_children(fileref)) {
|
|
WARN("trying to rename file with open children\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
|
|
if (oldfileref) {
|
|
SeCaptureSubjectContext(&subjcont);
|
|
|
|
if (!SeAccessCheck(oldfileref->fcb->sd, &subjcont, false, DELETE, 0, NULL,
|
|
IoGetFileObjectGenericMapping(), Irp->RequestorMode, &access, &Status)) {
|
|
SeReleaseSubjectContext(&subjcont);
|
|
TRACE("SeAccessCheck failed, returning %08x\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
SeReleaseSubjectContext(&subjcont);
|
|
|
|
if (oldfileref->open_count > 0 && flags & FILE_RENAME_POSIX_SEMANTICS) {
|
|
oldfileref->delete_on_close = true;
|
|
oldfileref->posix_delete = true;
|
|
}
|
|
|
|
Status = delete_fileref(oldfileref, NULL, oldfileref->open_count > 0 && flags & FILE_RENAME_POSIX_SEMANTICS, Irp, &rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_fileref returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
if (fileref->parent->fcb->subvol != related->fcb->subvol && (fileref->fcb->subvol == fileref->parent->fcb->subvol || fileref->fcb == Vcb->dummy_fcb)) {
|
|
Status = move_across_subvols(fileref, ccb, related, &utf8, &fnus, Irp, &rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("move_across_subvols returned %08x\n", Status);
|
|
}
|
|
goto end;
|
|
}
|
|
|
|
if (related == fileref->parent) { // keeping file in same directory
|
|
UNICODE_STRING oldfn, newfn;
|
|
USHORT name_offset;
|
|
ULONG reqlen, oldutf8len;
|
|
|
|
oldfn.Length = oldfn.MaximumLength = 0;
|
|
|
|
Status = fileref_get_filename(fileref, &oldfn, &name_offset, &reqlen);
|
|
if (Status != STATUS_BUFFER_OVERFLOW) {
|
|
ERR("fileref_get_filename returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
oldfn.Buffer = ExAllocatePoolWithTag(PagedPool, reqlen, ALLOC_TAG);
|
|
if (!oldfn.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
oldfn.MaximumLength = (uint16_t)reqlen;
|
|
|
|
Status = fileref_get_filename(fileref, &oldfn, &name_offset, &reqlen);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("fileref_get_filename returned %08x\n", Status);
|
|
ExFreePool(oldfn.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
oldutf8len = fileref->dc->utf8.Length;
|
|
|
|
if (!fileref->created && !fileref->oldutf8.Buffer) {
|
|
fileref->oldutf8.Buffer = ExAllocatePoolWithTag(PagedPool, fileref->dc->utf8.Length, ALLOC_TAG);
|
|
if (!fileref->oldutf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
fileref->oldutf8.Length = fileref->oldutf8.MaximumLength = fileref->dc->utf8.Length;
|
|
RtlCopyMemory(fileref->oldutf8.Buffer, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);
|
|
}
|
|
|
|
TRACE("renaming %.*S to %.*S\n", fileref->dc->name.Length / sizeof(WCHAR), fileref->dc->name.Buffer, fnus.Length / sizeof(WCHAR), fnus.Buffer);
|
|
|
|
mark_fileref_dirty(fileref);
|
|
|
|
if (fileref->dc) {
|
|
ExAcquireResourceExclusiveLite(&fileref->parent->fcb->nonpaged->dir_children_lock, true);
|
|
|
|
ExFreePool(fileref->dc->utf8.Buffer);
|
|
ExFreePool(fileref->dc->name.Buffer);
|
|
ExFreePool(fileref->dc->name_uc.Buffer);
|
|
|
|
fileref->dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.Length, ALLOC_TAG);
|
|
if (!fileref->dc->utf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock);
|
|
ExFreePool(oldfn.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
fileref->dc->utf8.Length = fileref->dc->utf8.MaximumLength = utf8.Length;
|
|
RtlCopyMemory(fileref->dc->utf8.Buffer, utf8.Buffer, utf8.Length);
|
|
|
|
fileref->dc->name.Buffer = ExAllocatePoolWithTag(PagedPool, fnus.Length, ALLOC_TAG);
|
|
if (!fileref->dc->name.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock);
|
|
ExFreePool(oldfn.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
fileref->dc->name.Length = fileref->dc->name.MaximumLength = fnus.Length;
|
|
RtlCopyMemory(fileref->dc->name.Buffer, fnus.Buffer, fnus.Length);
|
|
|
|
Status = RtlUpcaseUnicodeString(&fileref->dc->name_uc, &fileref->dc->name, true);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("RtlUpcaseUnicodeString returned %08x\n", Status);
|
|
ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock);
|
|
ExFreePool(oldfn.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
remove_dir_child_from_hash_lists(fileref->parent->fcb, fileref->dc);
|
|
|
|
fileref->dc->hash = calc_crc32c(0xffffffff, (uint8_t*)fileref->dc->name.Buffer, fileref->dc->name.Length);
|
|
fileref->dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)fileref->dc->name_uc.Buffer, fileref->dc->name_uc.Length);
|
|
|
|
insert_dir_child_into_hash_lists(fileref->parent->fcb, fileref->dc);
|
|
|
|
ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock);
|
|
}
|
|
|
|
newfn.Length = newfn.MaximumLength = 0;
|
|
|
|
Status = fileref_get_filename(fileref, &newfn, &name_offset, &reqlen);
|
|
if (Status != STATUS_BUFFER_OVERFLOW) {
|
|
ERR("fileref_get_filename returned %08x\n", Status);
|
|
ExFreePool(oldfn.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
newfn.Buffer = ExAllocatePoolWithTag(PagedPool, reqlen, ALLOC_TAG);
|
|
if (!newfn.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
ExFreePool(oldfn.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
newfn.MaximumLength = (uint16_t)reqlen;
|
|
|
|
Status = fileref_get_filename(fileref, &newfn, &name_offset, &reqlen);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("fileref_get_filename returned %08x\n", Status);
|
|
ExFreePool(oldfn.Buffer);
|
|
ExFreePool(newfn.Buffer);
|
|
goto end;
|
|
}
|
|
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &now);
|
|
|
|
if (fcb != Vcb->dummy_fcb && (fileref->parent->fcb->subvol == fcb->subvol || !is_subvol_readonly(fcb->subvol, Irp))) {
|
|
fcb->inode_item.transid = Vcb->superblock.generation;
|
|
fcb->inode_item.sequence++;
|
|
|
|
if (!ccb->user_set_change_time)
|
|
fcb->inode_item.st_ctime = now;
|
|
|
|
fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(fcb);
|
|
}
|
|
|
|
// update parent's INODE_ITEM
|
|
|
|
related->fcb->inode_item.transid = Vcb->superblock.generation;
|
|
TRACE("related->fcb->inode_item.st_size (inode %I64x) was %I64x\n", related->fcb->inode, related->fcb->inode_item.st_size);
|
|
related->fcb->inode_item.st_size = related->fcb->inode_item.st_size + (2 * utf8.Length) - (2* oldutf8len);
|
|
TRACE("related->fcb->inode_item.st_size (inode %I64x) now %I64x\n", related->fcb->inode, related->fcb->inode_item.st_size);
|
|
related->fcb->inode_item.sequence++;
|
|
related->fcb->inode_item.st_ctime = now;
|
|
related->fcb->inode_item.st_mtime = now;
|
|
|
|
related->fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(related->fcb);
|
|
send_notification_fileref(related, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);
|
|
|
|
FsRtlNotifyFilterReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&oldfn, name_offset, NULL, NULL,
|
|
fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_RENAMED_OLD_NAME, NULL, NULL);
|
|
FsRtlNotifyFilterReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&newfn, name_offset, NULL, NULL,
|
|
fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_RENAMED_NEW_NAME, NULL, NULL);
|
|
|
|
ExFreePool(oldfn.Buffer);
|
|
ExFreePool(newfn.Buffer);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
goto end;
|
|
}
|
|
|
|
// We move files by moving the existing fileref to the new directory, and
|
|
// replacing it with a dummy fileref with the same original values, but marked as deleted.
|
|
|
|
send_notification_fileref(fileref, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_REMOVED, NULL);
|
|
|
|
fr2 = create_fileref(Vcb);
|
|
|
|
fr2->fcb = fileref->fcb;
|
|
fr2->fcb->refcount++;
|
|
|
|
fr2->oldutf8 = fileref->oldutf8;
|
|
fr2->oldindex = fileref->dc->index;
|
|
fr2->delete_on_close = fileref->delete_on_close;
|
|
fr2->deleted = true;
|
|
fr2->created = fileref->created;
|
|
fr2->parent = fileref->parent;
|
|
fr2->dc = NULL;
|
|
|
|
if (!fr2->oldutf8.Buffer) {
|
|
fr2->oldutf8.Buffer = ExAllocatePoolWithTag(PagedPool, fileref->dc->utf8.Length, ALLOC_TAG);
|
|
if (!fr2->oldutf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(fr2->oldutf8.Buffer, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);
|
|
|
|
fr2->oldutf8.Length = fr2->oldutf8.MaximumLength = fileref->dc->utf8.Length;
|
|
}
|
|
|
|
if (fr2->fcb->type == BTRFS_TYPE_DIRECTORY)
|
|
fr2->fcb->fileref = fr2;
|
|
|
|
if (fileref->fcb->inode == SUBVOL_ROOT_INODE)
|
|
fileref->fcb->subvol->parent = related->fcb->subvol->id;
|
|
|
|
fileref->oldutf8.Length = fileref->oldutf8.MaximumLength = 0;
|
|
fileref->oldutf8.Buffer = NULL;
|
|
fileref->deleted = false;
|
|
fileref->created = true;
|
|
fileref->parent = related;
|
|
|
|
ExAcquireResourceExclusiveLite(&fileref->parent->fcb->nonpaged->dir_children_lock, true);
|
|
InsertHeadList(&fileref->list_entry, &fr2->list_entry);
|
|
RemoveEntryList(&fileref->list_entry);
|
|
ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock);
|
|
|
|
mark_fileref_dirty(fr2);
|
|
mark_fileref_dirty(fileref);
|
|
|
|
if (fileref->dc) {
|
|
// remove from old parent
|
|
ExAcquireResourceExclusiveLite(&fr2->parent->fcb->nonpaged->dir_children_lock, true);
|
|
RemoveEntryList(&fileref->dc->list_entry_index);
|
|
remove_dir_child_from_hash_lists(fr2->parent->fcb, fileref->dc);
|
|
ExReleaseResourceLite(&fr2->parent->fcb->nonpaged->dir_children_lock);
|
|
|
|
if (fileref->dc->utf8.Length != utf8.Length || RtlCompareMemory(fileref->dc->utf8.Buffer, utf8.Buffer, utf8.Length) != utf8.Length) {
|
|
// handle changed name
|
|
|
|
ExFreePool(fileref->dc->utf8.Buffer);
|
|
ExFreePool(fileref->dc->name.Buffer);
|
|
ExFreePool(fileref->dc->name_uc.Buffer);
|
|
|
|
fileref->dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.Length, ALLOC_TAG);
|
|
if (!fileref->dc->utf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
fileref->dc->utf8.Length = fileref->dc->utf8.MaximumLength = utf8.Length;
|
|
RtlCopyMemory(fileref->dc->utf8.Buffer, utf8.Buffer, utf8.Length);
|
|
|
|
fileref->dc->name.Buffer = ExAllocatePoolWithTag(PagedPool, fnus.Length, ALLOC_TAG);
|
|
if (!fileref->dc->name.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
fileref->dc->name.Length = fileref->dc->name.MaximumLength = fnus.Length;
|
|
RtlCopyMemory(fileref->dc->name.Buffer, fnus.Buffer, fnus.Length);
|
|
|
|
Status = RtlUpcaseUnicodeString(&fileref->dc->name_uc, &fileref->dc->name, true);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("RtlUpcaseUnicodeString returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
fileref->dc->hash = calc_crc32c(0xffffffff, (uint8_t*)fileref->dc->name.Buffer, fileref->dc->name.Length);
|
|
fileref->dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)fileref->dc->name_uc.Buffer, fileref->dc->name_uc.Length);
|
|
}
|
|
|
|
// add to new parent
|
|
ExAcquireResourceExclusiveLite(&related->fcb->nonpaged->dir_children_lock, true);
|
|
|
|
if (IsListEmpty(&related->fcb->dir_children_index))
|
|
fileref->dc->index = 2;
|
|
else {
|
|
dir_child* dc2 = CONTAINING_RECORD(related->fcb->dir_children_index.Blink, dir_child, list_entry_index);
|
|
|
|
fileref->dc->index = max(2, dc2->index + 1);
|
|
}
|
|
|
|
InsertTailList(&related->fcb->dir_children_index, &fileref->dc->list_entry_index);
|
|
insert_dir_child_into_hash_lists(related->fcb, fileref->dc);
|
|
ExReleaseResourceLite(&related->fcb->nonpaged->dir_children_lock);
|
|
}
|
|
|
|
ExAcquireResourceExclusiveLite(&related->fcb->nonpaged->dir_children_lock, true);
|
|
InsertTailList(&related->children, &fileref->list_entry);
|
|
ExReleaseResourceLite(&related->fcb->nonpaged->dir_children_lock);
|
|
|
|
if (fcb->inode_item.st_nlink > 1) {
|
|
// add new hardlink entry to fcb
|
|
|
|
hl = ExAllocatePoolWithTag(PagedPool, sizeof(hardlink), ALLOC_TAG);
|
|
if (!hl) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
hl->parent = related->fcb->inode;
|
|
hl->index = fileref->dc->index;
|
|
|
|
hl->name.Length = hl->name.MaximumLength = fnus.Length;
|
|
hl->name.Buffer = ExAllocatePoolWithTag(PagedPool, hl->name.MaximumLength, ALLOC_TAG);
|
|
|
|
if (!hl->name.Buffer) {
|
|
ERR("out of memory\n");
|
|
ExFreePool(hl);
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(hl->name.Buffer, fnus.Buffer, fnus.Length);
|
|
|
|
hl->utf8.Length = hl->utf8.MaximumLength = fileref->dc->utf8.Length;
|
|
hl->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, hl->utf8.MaximumLength, ALLOC_TAG);
|
|
|
|
if (!hl->utf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
ExFreePool(hl->name.Buffer);
|
|
ExFreePool(hl);
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(hl->utf8.Buffer, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);
|
|
|
|
InsertTailList(&fcb->hardlinks, &hl->list_entry);
|
|
}
|
|
|
|
// delete old hardlink entry from fcb
|
|
|
|
le = fcb->hardlinks.Flink;
|
|
while (le != &fcb->hardlinks) {
|
|
hl = CONTAINING_RECORD(le, hardlink, list_entry);
|
|
|
|
if (hl->parent == fr2->parent->fcb->inode && hl->index == fr2->oldindex) {
|
|
RemoveEntryList(&hl->list_entry);
|
|
|
|
if (hl->utf8.Buffer)
|
|
ExFreePool(hl->utf8.Buffer);
|
|
|
|
if (hl->name.Buffer)
|
|
ExFreePool(hl->name.Buffer);
|
|
|
|
ExFreePool(hl);
|
|
break;
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
// update inode's INODE_ITEM
|
|
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &now);
|
|
|
|
if (fcb != Vcb->dummy_fcb && (fileref->parent->fcb->subvol == fcb->subvol || !is_subvol_readonly(fcb->subvol, Irp))) {
|
|
fcb->inode_item.transid = Vcb->superblock.generation;
|
|
fcb->inode_item.sequence++;
|
|
|
|
if (!ccb->user_set_change_time)
|
|
fcb->inode_item.st_ctime = now;
|
|
|
|
fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(fcb);
|
|
}
|
|
|
|
// update new parent's INODE_ITEM
|
|
|
|
related->fcb->inode_item.transid = Vcb->superblock.generation;
|
|
TRACE("related->fcb->inode_item.st_size (inode %I64x) was %I64x\n", related->fcb->inode, related->fcb->inode_item.st_size);
|
|
related->fcb->inode_item.st_size += 2 * utf8len;
|
|
TRACE("related->fcb->inode_item.st_size (inode %I64x) now %I64x\n", related->fcb->inode, related->fcb->inode_item.st_size);
|
|
related->fcb->inode_item.sequence++;
|
|
related->fcb->inode_item.st_ctime = now;
|
|
related->fcb->inode_item.st_mtime = now;
|
|
|
|
related->fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(related->fcb);
|
|
|
|
// update old parent's INODE_ITEM
|
|
|
|
fr2->parent->fcb->inode_item.transid = Vcb->superblock.generation;
|
|
TRACE("fr2->parent->fcb->inode_item.st_size (inode %I64x) was %I64x\n", fr2->parent->fcb->inode, fr2->parent->fcb->inode_item.st_size);
|
|
fr2->parent->fcb->inode_item.st_size -= 2 * origutf8len;
|
|
TRACE("fr2->parent->fcb->inode_item.st_size (inode %I64x) now %I64x\n", fr2->parent->fcb->inode, fr2->parent->fcb->inode_item.st_size);
|
|
fr2->parent->fcb->inode_item.sequence++;
|
|
fr2->parent->fcb->inode_item.st_ctime = now;
|
|
fr2->parent->fcb->inode_item.st_mtime = now;
|
|
|
|
free_fileref(fr2);
|
|
|
|
fr2->parent->fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(fr2->parent->fcb);
|
|
|
|
send_notification_fileref(fileref, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_ADDED, NULL);
|
|
send_notification_fileref(related, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);
|
|
send_notification_fileref(fr2->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
end:
|
|
if (oldfileref)
|
|
free_fileref(oldfileref);
|
|
|
|
if (!NT_SUCCESS(Status) && related)
|
|
free_fileref(related);
|
|
|
|
if (!NT_SUCCESS(Status) && fr2)
|
|
free_fileref(fr2);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
clear_rollback(&rollback);
|
|
else
|
|
do_rollback(Vcb, &rollback);
|
|
|
|
ExReleaseResourceLite(fcb->Header.Resource);
|
|
ExReleaseResourceLite(&Vcb->fileref_lock);
|
|
ExReleaseResourceLite(&Vcb->tree_lock);
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS stream_set_end_of_file_information(device_extension* Vcb, uint16_t end, fcb* fcb, file_ref* fileref, bool advance_only) {
|
|
LARGE_INTEGER time;
|
|
BTRFS_TIME now;
|
|
|
|
TRACE("setting new end to %I64x bytes (currently %x)\n", end, fcb->adsdata.Length);
|
|
|
|
if (!fileref || !fileref->parent) {
|
|
ERR("no fileref for stream\n");
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
if (end < fcb->adsdata.Length) {
|
|
if (advance_only)
|
|
return STATUS_SUCCESS;
|
|
|
|
TRACE("truncating stream to %I64x bytes\n", end);
|
|
|
|
fcb->adsdata.Length = end;
|
|
} else if (end > fcb->adsdata.Length) {
|
|
TRACE("extending stream to %I64x bytes\n", end);
|
|
|
|
if (end > fcb->adsmaxlen) {
|
|
ERR("error - xattr too long (%u > %u)\n", end, fcb->adsmaxlen);
|
|
return STATUS_DISK_FULL;
|
|
}
|
|
|
|
if (end > fcb->adsdata.MaximumLength) {
|
|
char* data = ExAllocatePoolWithTag(PagedPool, end, ALLOC_TAG);
|
|
if (!data) {
|
|
ERR("out of memory\n");
|
|
ExFreePool(data);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
if (fcb->adsdata.Buffer) {
|
|
RtlCopyMemory(data, fcb->adsdata.Buffer, fcb->adsdata.Length);
|
|
ExFreePool(fcb->adsdata.Buffer);
|
|
}
|
|
|
|
fcb->adsdata.Buffer = data;
|
|
fcb->adsdata.MaximumLength = end;
|
|
}
|
|
|
|
RtlZeroMemory(&fcb->adsdata.Buffer[fcb->adsdata.Length], end - fcb->adsdata.Length);
|
|
|
|
fcb->adsdata.Length = end;
|
|
}
|
|
|
|
mark_fcb_dirty(fcb);
|
|
|
|
fcb->Header.AllocationSize.QuadPart = end;
|
|
fcb->Header.FileSize.QuadPart = end;
|
|
fcb->Header.ValidDataLength.QuadPart = end;
|
|
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &now);
|
|
|
|
fileref->parent->fcb->inode_item.transid = Vcb->superblock.generation;
|
|
fileref->parent->fcb->inode_item.sequence++;
|
|
fileref->parent->fcb->inode_item.st_ctime = now;
|
|
|
|
fileref->parent->fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(fileref->parent->fcb);
|
|
|
|
fileref->parent->fcb->subvol->root_item.ctransid = Vcb->superblock.generation;
|
|
fileref->parent->fcb->subvol->root_item.ctime = now;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS set_end_of_file_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, bool advance_only, bool prealloc) {
|
|
FILE_END_OF_FILE_INFORMATION* feofi = Irp->AssociatedIrp.SystemBuffer;
|
|
fcb* fcb = FileObject->FsContext;
|
|
ccb* ccb = FileObject->FsContext2;
|
|
file_ref* fileref = ccb ? ccb->fileref : NULL;
|
|
NTSTATUS Status;
|
|
LARGE_INTEGER time;
|
|
CC_FILE_SIZES ccfs;
|
|
LIST_ENTRY rollback;
|
|
bool set_size = false;
|
|
ULONG filter;
|
|
|
|
if (!fileref) {
|
|
ERR("fileref is NULL\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
InitializeListHead(&rollback);
|
|
|
|
ExAcquireResourceSharedLite(&Vcb->tree_lock, true);
|
|
|
|
ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);
|
|
|
|
if (fileref ? fileref->deleted : fcb->deleted) {
|
|
Status = STATUS_FILE_CLOSED;
|
|
goto end;
|
|
}
|
|
|
|
if (fcb->ads) {
|
|
if (feofi->EndOfFile.QuadPart > 0xffff) {
|
|
Status = STATUS_DISK_FULL;
|
|
goto end;
|
|
}
|
|
|
|
if (feofi->EndOfFile.QuadPart < 0) {
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
Status = stream_set_end_of_file_information(Vcb, (uint16_t)feofi->EndOfFile.QuadPart, fcb, fileref, advance_only);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
ccfs.AllocationSize = fcb->Header.AllocationSize;
|
|
ccfs.FileSize = fcb->Header.FileSize;
|
|
ccfs.ValidDataLength = fcb->Header.ValidDataLength;
|
|
set_size = true;
|
|
}
|
|
|
|
filter = FILE_NOTIFY_CHANGE_STREAM_SIZE;
|
|
|
|
if (!ccb->user_set_write_time) {
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &fileref->parent->fcb->inode_item.st_mtime);
|
|
filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
|
|
|
|
fileref->parent->fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(fileref->parent->fcb);
|
|
}
|
|
|
|
queue_notification_fcb(fileref->parent, filter, FILE_ACTION_MODIFIED_STREAM, &fileref->dc->name);
|
|
|
|
goto end;
|
|
}
|
|
|
|
TRACE("file: %p\n", FileObject);
|
|
TRACE("paging IO: %s\n", Irp->Flags & IRP_PAGING_IO ? "true" : "false");
|
|
TRACE("FileObject: AllocationSize = %I64x, FileSize = %I64x, ValidDataLength = %I64x\n",
|
|
fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart);
|
|
|
|
TRACE("setting new end to %I64x bytes (currently %I64x)\n", feofi->EndOfFile.QuadPart, fcb->inode_item.st_size);
|
|
|
|
if ((uint64_t)feofi->EndOfFile.QuadPart < fcb->inode_item.st_size) {
|
|
if (advance_only) {
|
|
Status = STATUS_SUCCESS;
|
|
goto end;
|
|
}
|
|
|
|
TRACE("truncating file to %I64x bytes\n", feofi->EndOfFile.QuadPart);
|
|
|
|
if (!MmCanFileBeTruncated(&fcb->nonpaged->segment_object, &feofi->EndOfFile)) {
|
|
Status = STATUS_USER_MAPPED_FILE;
|
|
goto end;
|
|
}
|
|
|
|
Status = truncate_file(fcb, feofi->EndOfFile.QuadPart, Irp, &rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - truncate_file failed\n");
|
|
goto end;
|
|
}
|
|
} else if ((uint64_t)feofi->EndOfFile.QuadPart > fcb->inode_item.st_size) {
|
|
if (Irp->Flags & IRP_PAGING_IO) {
|
|
TRACE("paging IO tried to extend file size\n");
|
|
Status = STATUS_SUCCESS;
|
|
goto end;
|
|
}
|
|
|
|
TRACE("extending file to %I64x bytes\n", feofi->EndOfFile.QuadPart);
|
|
|
|
Status = extend_file(fcb, fileref, feofi->EndOfFile.QuadPart, prealloc, NULL, &rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("error - extend_file failed\n");
|
|
goto end;
|
|
}
|
|
} else if ((uint64_t)feofi->EndOfFile.QuadPart == fcb->inode_item.st_size && advance_only) {
|
|
Status = STATUS_SUCCESS;
|
|
goto end;
|
|
}
|
|
|
|
ccfs.AllocationSize = fcb->Header.AllocationSize;
|
|
ccfs.FileSize = fcb->Header.FileSize;
|
|
ccfs.ValidDataLength = fcb->Header.ValidDataLength;
|
|
set_size = true;
|
|
|
|
filter = FILE_NOTIFY_CHANGE_SIZE;
|
|
|
|
if (!ccb->user_set_write_time) {
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &fcb->inode_item.st_mtime);
|
|
filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
|
|
}
|
|
|
|
fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(fcb);
|
|
queue_notification_fcb(fileref, filter, FILE_ACTION_MODIFIED, NULL);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
end:
|
|
if (NT_SUCCESS(Status))
|
|
clear_rollback(&rollback);
|
|
else
|
|
do_rollback(Vcb, &rollback);
|
|
|
|
ExReleaseResourceLite(fcb->Header.Resource);
|
|
|
|
if (set_size) {
|
|
_SEH2_TRY {
|
|
CcSetFileSizes(FileObject, &ccfs);
|
|
} _SEH2_EXCEPT (EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = _SEH2_GetExceptionCode();
|
|
} _SEH2_END;
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
ERR("CcSetFileSizes threw exception %08x\n", Status);
|
|
}
|
|
|
|
ExReleaseResourceLite(&Vcb->tree_lock);
|
|
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS set_position_information(PFILE_OBJECT FileObject, PIRP Irp) {
|
|
FILE_POSITION_INFORMATION* fpi = (FILE_POSITION_INFORMATION*)Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("setting the position on %p to %I64x\n", FileObject, fpi->CurrentByteOffset.QuadPart);
|
|
|
|
// FIXME - make sure aligned for FO_NO_INTERMEDIATE_BUFFERING
|
|
|
|
FileObject->CurrentByteOffset = fpi->CurrentByteOffset;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS set_link_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, PFILE_OBJECT tfo, bool ex) {
|
|
FILE_LINK_INFORMATION_EX* fli = Irp->AssociatedIrp.SystemBuffer;
|
|
fcb *fcb = FileObject->FsContext, *tfofcb, *parfcb;
|
|
ccb* ccb = FileObject->FsContext2;
|
|
file_ref *fileref = ccb ? ccb->fileref : NULL, *oldfileref = NULL, *related = NULL, *fr2 = NULL;
|
|
WCHAR* fn;
|
|
ULONG fnlen, utf8len;
|
|
UNICODE_STRING fnus;
|
|
ANSI_STRING utf8;
|
|
NTSTATUS Status;
|
|
LARGE_INTEGER time;
|
|
BTRFS_TIME now;
|
|
LIST_ENTRY rollback;
|
|
hardlink* hl;
|
|
ACCESS_MASK access;
|
|
SECURITY_SUBJECT_CONTEXT subjcont;
|
|
dir_child* dc = NULL;
|
|
ULONG flags;
|
|
|
|
InitializeListHead(&rollback);
|
|
|
|
// FIXME - check fli length
|
|
// FIXME - don't ignore fli->RootDirectory
|
|
|
|
if (ex)
|
|
flags = fli->Flags;
|
|
else
|
|
flags = fli->ReplaceIfExists ? FILE_LINK_REPLACE_IF_EXISTS : 0;
|
|
|
|
TRACE("flags = %x\n", flags);
|
|
TRACE("RootDirectory = %p\n", fli->RootDirectory);
|
|
TRACE("FileNameLength = %x\n", fli->FileNameLength);
|
|
TRACE("FileName = %.*S\n", fli->FileNameLength / sizeof(WCHAR), fli->FileName);
|
|
|
|
fn = fli->FileName;
|
|
fnlen = fli->FileNameLength / sizeof(WCHAR);
|
|
|
|
if (!tfo) {
|
|
if (!fileref || !fileref->parent) {
|
|
ERR("no fileref set and no directory given\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
parfcb = fileref->parent->fcb;
|
|
tfofcb = NULL;
|
|
} else {
|
|
LONG i;
|
|
|
|
tfofcb = tfo->FsContext;
|
|
parfcb = tfofcb;
|
|
|
|
while (fnlen > 0 && (fli->FileName[fnlen - 1] == '/' || fli->FileName[fnlen - 1] == '\\'))
|
|
fnlen--;
|
|
|
|
if (fnlen == 0)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
for (i = fnlen - 1; i >= 0; i--) {
|
|
if (fli->FileName[i] == '\\' || fli->FileName[i] == '/') {
|
|
fn = &fli->FileName[i+1];
|
|
fnlen = (fli->FileNameLength / sizeof(WCHAR)) - i - 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ExAcquireResourceSharedLite(&Vcb->tree_lock, true);
|
|
ExAcquireResourceExclusiveLite(&Vcb->fileref_lock, true);
|
|
ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);
|
|
|
|
if (fcb->type == BTRFS_TYPE_DIRECTORY) {
|
|
WARN("tried to create hard link on directory\n");
|
|
Status = STATUS_FILE_IS_A_DIRECTORY;
|
|
goto end;
|
|
}
|
|
|
|
if (fcb->ads) {
|
|
WARN("tried to create hard link on stream\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
if (fcb->inode_item.st_nlink >= 65535) {
|
|
Status = STATUS_TOO_MANY_LINKS;
|
|
goto end;
|
|
}
|
|
|
|
fnus.Buffer = fn;
|
|
fnus.Length = fnus.MaximumLength = (uint16_t)(fnlen * sizeof(WCHAR));
|
|
|
|
TRACE("fnus = %.*S\n", fnus.Length / sizeof(WCHAR), fnus.Buffer);
|
|
|
|
Status = utf16_to_utf8(NULL, 0, &utf8len, fn, (ULONG)fnlen * sizeof(WCHAR));
|
|
if (!NT_SUCCESS(Status))
|
|
goto end;
|
|
|
|
utf8.MaximumLength = utf8.Length = (uint16_t)utf8len;
|
|
utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.MaximumLength, ALLOC_TAG);
|
|
if (!utf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
Status = utf16_to_utf8(utf8.Buffer, utf8len, &utf8len, fn, (ULONG)fnlen * sizeof(WCHAR));
|
|
if (!NT_SUCCESS(Status))
|
|
goto end;
|
|
|
|
if (tfo && tfo->FsContext2) {
|
|
struct _ccb* relatedccb = tfo->FsContext2;
|
|
|
|
related = relatedccb->fileref;
|
|
increase_fileref_refcount(related);
|
|
}
|
|
|
|
Status = open_fileref(Vcb, &oldfileref, &fnus, related, false, NULL, NULL, PagedPool, ccb->case_sensitive, Irp);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
if (!oldfileref->deleted) {
|
|
WARN("destination file already exists\n");
|
|
|
|
if (!(flags & FILE_LINK_REPLACE_IF_EXISTS)) {
|
|
Status = STATUS_OBJECT_NAME_COLLISION;
|
|
goto end;
|
|
} else if (fileref == oldfileref) {
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
} else if (!(flags & FILE_LINK_POSIX_SEMANTICS) && (oldfileref->open_count > 0 || has_open_children(oldfileref)) && !oldfileref->deleted) {
|
|
WARN("trying to overwrite open file\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
} else if (!(flags & FILE_LINK_IGNORE_READONLY_ATTRIBUTE) && oldfileref->fcb->atts & FILE_ATTRIBUTE_READONLY) {
|
|
WARN("trying to overwrite readonly file\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
} else if (oldfileref->fcb->type == BTRFS_TYPE_DIRECTORY) {
|
|
WARN("trying to overwrite directory\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
} else {
|
|
free_fileref(oldfileref);
|
|
oldfileref = NULL;
|
|
}
|
|
}
|
|
|
|
if (!related) {
|
|
Status = open_fileref(Vcb, &related, &fnus, NULL, true, NULL, NULL, PagedPool, ccb->case_sensitive, Irp);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("open_fileref returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
SeCaptureSubjectContext(&subjcont);
|
|
|
|
if (!SeAccessCheck(related->fcb->sd, &subjcont, false, FILE_ADD_FILE, 0, NULL,
|
|
IoGetFileObjectGenericMapping(), Irp->RequestorMode, &access, &Status)) {
|
|
SeReleaseSubjectContext(&subjcont);
|
|
TRACE("SeAccessCheck failed, returning %08x\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
SeReleaseSubjectContext(&subjcont);
|
|
|
|
if (fcb->subvol != parfcb->subvol) {
|
|
WARN("can't create hard link over subvolume boundary\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
if (oldfileref) {
|
|
SeCaptureSubjectContext(&subjcont);
|
|
|
|
if (!SeAccessCheck(oldfileref->fcb->sd, &subjcont, false, DELETE, 0, NULL,
|
|
IoGetFileObjectGenericMapping(), Irp->RequestorMode, &access, &Status)) {
|
|
SeReleaseSubjectContext(&subjcont);
|
|
TRACE("SeAccessCheck failed, returning %08x\n", Status);
|
|
goto end;
|
|
}
|
|
|
|
SeReleaseSubjectContext(&subjcont);
|
|
|
|
if (oldfileref->open_count > 0 && flags & FILE_RENAME_POSIX_SEMANTICS) {
|
|
oldfileref->delete_on_close = true;
|
|
oldfileref->posix_delete = true;
|
|
}
|
|
|
|
Status = delete_fileref(oldfileref, NULL, oldfileref->open_count > 0 && flags & FILE_RENAME_POSIX_SEMANTICS, Irp, &rollback);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("delete_fileref returned %08x\n", Status);
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
fr2 = create_fileref(Vcb);
|
|
|
|
fr2->fcb = fcb;
|
|
fcb->refcount++;
|
|
|
|
fr2->created = true;
|
|
fr2->parent = related;
|
|
|
|
Status = add_dir_child(related->fcb, fcb->inode, false, &utf8, &fnus, fcb->type, &dc);
|
|
if (!NT_SUCCESS(Status))
|
|
WARN("add_dir_child returned %08x\n", Status);
|
|
|
|
fr2->dc = dc;
|
|
dc->fileref = fr2;
|
|
|
|
ExAcquireResourceExclusiveLite(&related->fcb->nonpaged->dir_children_lock, true);
|
|
InsertTailList(&related->children, &fr2->list_entry);
|
|
ExReleaseResourceLite(&related->fcb->nonpaged->dir_children_lock);
|
|
|
|
// add hardlink for existing fileref, if it's not there already
|
|
if (IsListEmpty(&fcb->hardlinks)) {
|
|
hl = ExAllocatePoolWithTag(PagedPool, sizeof(hardlink), ALLOC_TAG);
|
|
if (!hl) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
hl->parent = fileref->parent->fcb->inode;
|
|
hl->index = fileref->dc->index;
|
|
|
|
hl->name.Length = hl->name.MaximumLength = fnus.Length;
|
|
hl->name.Buffer = ExAllocatePoolWithTag(PagedPool, fnus.Length, ALLOC_TAG);
|
|
|
|
if (!hl->name.Buffer) {
|
|
ERR("out of memory\n");
|
|
ExFreePool(hl);
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(hl->name.Buffer, fnus.Buffer, fnus.Length);
|
|
|
|
hl->utf8.Length = hl->utf8.MaximumLength = fileref->dc->utf8.Length;
|
|
hl->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, hl->utf8.MaximumLength, ALLOC_TAG);
|
|
|
|
if (!hl->utf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
ExFreePool(hl->name.Buffer);
|
|
ExFreePool(hl);
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(hl->utf8.Buffer, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);
|
|
|
|
InsertTailList(&fcb->hardlinks, &hl->list_entry);
|
|
}
|
|
|
|
hl = ExAllocatePoolWithTag(PagedPool, sizeof(hardlink), ALLOC_TAG);
|
|
if (!hl) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
hl->parent = related->fcb->inode;
|
|
hl->index = dc->index;
|
|
|
|
hl->name.Length = hl->name.MaximumLength = fnus.Length;
|
|
hl->name.Buffer = ExAllocatePoolWithTag(PagedPool, hl->name.MaximumLength, ALLOC_TAG);
|
|
|
|
if (!hl->name.Buffer) {
|
|
ERR("out of memory\n");
|
|
ExFreePool(hl);
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(hl->name.Buffer, fnus.Buffer, fnus.Length);
|
|
|
|
hl->utf8.Length = hl->utf8.MaximumLength = utf8.Length;
|
|
hl->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, hl->utf8.MaximumLength, ALLOC_TAG);
|
|
|
|
if (!hl->utf8.Buffer) {
|
|
ERR("out of memory\n");
|
|
ExFreePool(hl->name.Buffer);
|
|
ExFreePool(hl);
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end;
|
|
}
|
|
|
|
RtlCopyMemory(hl->utf8.Buffer, utf8.Buffer, utf8.Length);
|
|
ExFreePool(utf8.Buffer);
|
|
|
|
InsertTailList(&fcb->hardlinks, &hl->list_entry);
|
|
|
|
mark_fileref_dirty(fr2);
|
|
free_fileref(fr2);
|
|
|
|
// update inode's INODE_ITEM
|
|
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &now);
|
|
|
|
fcb->inode_item.transid = Vcb->superblock.generation;
|
|
fcb->inode_item.sequence++;
|
|
fcb->inode_item.st_nlink++;
|
|
|
|
if (!ccb->user_set_change_time)
|
|
fcb->inode_item.st_ctime = now;
|
|
|
|
fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(fcb);
|
|
|
|
// update parent's INODE_ITEM
|
|
|
|
parfcb->inode_item.transid = Vcb->superblock.generation;
|
|
TRACE("parfcb->inode_item.st_size (inode %I64x) was %I64x\n", parfcb->inode, parfcb->inode_item.st_size);
|
|
parfcb->inode_item.st_size += 2 * utf8len;
|
|
TRACE("parfcb->inode_item.st_size (inode %I64x) now %I64x\n", parfcb->inode, parfcb->inode_item.st_size);
|
|
parfcb->inode_item.sequence++;
|
|
parfcb->inode_item.st_ctime = now;
|
|
|
|
parfcb->inode_item_changed = true;
|
|
mark_fcb_dirty(parfcb);
|
|
|
|
send_notification_fileref(fr2, FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_ADDED, NULL);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
end:
|
|
if (oldfileref)
|
|
free_fileref(oldfileref);
|
|
|
|
if (!NT_SUCCESS(Status) && related)
|
|
free_fileref(related);
|
|
|
|
if (!NT_SUCCESS(Status) && fr2)
|
|
free_fileref(fr2);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
clear_rollback(&rollback);
|
|
else
|
|
do_rollback(Vcb, &rollback);
|
|
|
|
ExReleaseResourceLite(fcb->Header.Resource);
|
|
ExReleaseResourceLite(&Vcb->fileref_lock);
|
|
ExReleaseResourceLite(&Vcb->tree_lock);
|
|
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS set_valid_data_length_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject) {
|
|
FILE_VALID_DATA_LENGTH_INFORMATION* fvdli = Irp->AssociatedIrp.SystemBuffer;
|
|
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
fcb* fcb = FileObject->FsContext;
|
|
ccb* ccb = FileObject->FsContext2;
|
|
file_ref* fileref = ccb ? ccb->fileref : NULL;
|
|
NTSTATUS Status;
|
|
LARGE_INTEGER time;
|
|
CC_FILE_SIZES ccfs;
|
|
LIST_ENTRY rollback;
|
|
bool set_size = false;
|
|
ULONG filter;
|
|
|
|
if (IrpSp->Parameters.SetFile.Length < sizeof(FILE_VALID_DATA_LENGTH_INFORMATION)) {
|
|
ERR("input buffer length was %u, expected %u\n", IrpSp->Parameters.SetFile.Length, sizeof(FILE_VALID_DATA_LENGTH_INFORMATION));
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (!fileref) {
|
|
ERR("fileref is NULL\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
InitializeListHead(&rollback);
|
|
|
|
ExAcquireResourceSharedLite(&Vcb->tree_lock, true);
|
|
|
|
ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);
|
|
|
|
if (fcb->atts & FILE_ATTRIBUTE_SPARSE_FILE) {
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
if (fvdli->ValidDataLength.QuadPart <= fcb->Header.ValidDataLength.QuadPart || fvdli->ValidDataLength.QuadPart > fcb->Header.FileSize.QuadPart) {
|
|
TRACE("invalid VDL of %I64u (current VDL = %I64u, file size = %I64u)\n", fvdli->ValidDataLength.QuadPart,
|
|
fcb->Header.ValidDataLength.QuadPart, fcb->Header.FileSize.QuadPart);
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
if (fileref ? fileref->deleted : fcb->deleted) {
|
|
Status = STATUS_FILE_CLOSED;
|
|
goto end;
|
|
}
|
|
|
|
// This function doesn't really do anything - the fsctl can only increase the value of ValidDataLength,
|
|
// and we set it to the max anyway.
|
|
|
|
ccfs.AllocationSize = fcb->Header.AllocationSize;
|
|
ccfs.FileSize = fcb->Header.FileSize;
|
|
ccfs.ValidDataLength = fvdli->ValidDataLength;
|
|
set_size = true;
|
|
|
|
filter = FILE_NOTIFY_CHANGE_SIZE;
|
|
|
|
if (!ccb->user_set_write_time) {
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &fcb->inode_item.st_mtime);
|
|
filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;
|
|
}
|
|
|
|
fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(fcb);
|
|
|
|
queue_notification_fcb(fileref, filter, FILE_ACTION_MODIFIED, NULL);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
end:
|
|
if (NT_SUCCESS(Status))
|
|
clear_rollback(&rollback);
|
|
else
|
|
do_rollback(Vcb, &rollback);
|
|
|
|
ExReleaseResourceLite(fcb->Header.Resource);
|
|
|
|
if (set_size) {
|
|
_SEH2_TRY {
|
|
CcSetFileSizes(FileObject, &ccfs);
|
|
} _SEH2_EXCEPT (EXCEPTION_EXECUTE_HANDLER) {
|
|
Status = _SEH2_GetExceptionCode();
|
|
} _SEH2_END;
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
ERR("CcSetFileSizes threw exception %08x\n", Status);
|
|
else
|
|
fcb->Header.AllocationSize = ccfs.AllocationSize;
|
|
}
|
|
|
|
ExReleaseResourceLite(&Vcb->tree_lock);
|
|
|
|
return Status;
|
|
}
|
|
|
|
#ifndef __REACTOS__
|
|
static NTSTATUS set_case_sensitive_information(PIRP Irp) {
|
|
FILE_CASE_SENSITIVE_INFORMATION* fcsi = (FILE_CASE_SENSITIVE_INFORMATION*)Irp->AssociatedIrp.SystemBuffer;
|
|
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
if (IrpSp->Parameters.FileSystemControl.InputBufferLength < sizeof(FILE_CASE_SENSITIVE_INFORMATION))
|
|
return STATUS_INFO_LENGTH_MISMATCH;
|
|
|
|
PFILE_OBJECT FileObject = IrpSp->FileObject;
|
|
|
|
if (!FileObject)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
fcb* fcb = FileObject->FsContext;
|
|
|
|
if (!fcb)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
if (!(fcb->atts & FILE_ATTRIBUTE_DIRECTORY)) {
|
|
WARN("cannot set case-sensitive flag on anything other than directory\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true);
|
|
|
|
fcb->case_sensitive = fcsi->Flags & FILE_CS_FLAG_CASE_SENSITIVE_DIR;
|
|
mark_fcb_dirty(fcb);
|
|
|
|
ExReleaseResourceLite(&fcb->Vcb->tree_lock);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
_Dispatch_type_(IRP_MJ_SET_INFORMATION)
|
|
_Function_class_(DRIVER_DISPATCH)
|
|
NTSTATUS __stdcall drv_set_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
|
|
NTSTATUS Status;
|
|
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
device_extension* Vcb = DeviceObject->DeviceExtension;
|
|
fcb* fcb = IrpSp->FileObject->FsContext;
|
|
ccb* ccb = IrpSp->FileObject->FsContext2;
|
|
bool top_level;
|
|
|
|
FsRtlEnterFileSystem();
|
|
|
|
top_level = is_top_level(Irp);
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {
|
|
Status = vol_set_information(DeviceObject, Irp);
|
|
goto end;
|
|
} else if (!Vcb || Vcb->type != VCB_TYPE_FS) {
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
if (!(Vcb->Vpb->Flags & VPB_MOUNTED)) {
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
|
|
if (Vcb->readonly && IrpSp->Parameters.SetFile.FileInformationClass != FilePositionInformation) {
|
|
Status = STATUS_MEDIA_WRITE_PROTECTED;
|
|
goto end;
|
|
}
|
|
|
|
if (!fcb) {
|
|
ERR("no fcb\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
if (!ccb) {
|
|
ERR("no ccb\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
if (fcb != Vcb->dummy_fcb && is_subvol_readonly(fcb->subvol, Irp) && IrpSp->Parameters.SetFile.FileInformationClass != FilePositionInformation &&
|
|
#ifndef __REACTOS__
|
|
(fcb->inode != SUBVOL_ROOT_INODE || (IrpSp->Parameters.SetFile.FileInformationClass != FileBasicInformation && IrpSp->Parameters.SetFile.FileInformationClass != FileRenameInformation && IrpSp->Parameters.SetFile.FileInformationClass != FileRenameInformationEx))) {
|
|
#else
|
|
(fcb->inode != SUBVOL_ROOT_INODE || (IrpSp->Parameters.SetFile.FileInformationClass != FileBasicInformation && IrpSp->Parameters.SetFile.FileInformationClass != FileRenameInformation))) {
|
|
#endif
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
|
|
Status = STATUS_NOT_IMPLEMENTED;
|
|
|
|
TRACE("set information\n");
|
|
|
|
FsRtlCheckOplock(fcb_oplock(fcb), Irp, NULL, NULL, NULL);
|
|
|
|
switch (IrpSp->Parameters.SetFile.FileInformationClass) {
|
|
case FileAllocationInformation:
|
|
{
|
|
TRACE("FileAllocationInformation\n");
|
|
|
|
if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_DATA)) {
|
|
WARN("insufficient privileges\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
break;
|
|
}
|
|
|
|
Status = set_end_of_file_information(Vcb, Irp, IrpSp->FileObject, false, true);
|
|
break;
|
|
}
|
|
|
|
case FileBasicInformation:
|
|
{
|
|
TRACE("FileBasicInformation\n");
|
|
|
|
if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_ATTRIBUTES)) {
|
|
WARN("insufficient privileges\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
break;
|
|
}
|
|
|
|
Status = set_basic_information(Vcb, Irp, IrpSp->FileObject);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileDispositionInformation:
|
|
{
|
|
TRACE("FileDispositionInformation\n");
|
|
|
|
if (Irp->RequestorMode == UserMode && !(ccb->access & DELETE)) {
|
|
WARN("insufficient privileges\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
break;
|
|
}
|
|
|
|
Status = set_disposition_information(Vcb, Irp, IrpSp->FileObject, false);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileEndOfFileInformation:
|
|
{
|
|
TRACE("FileEndOfFileInformation\n");
|
|
|
|
if (Irp->RequestorMode == UserMode && !(ccb->access & (FILE_WRITE_DATA | FILE_APPEND_DATA))) {
|
|
WARN("insufficient privileges\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
break;
|
|
}
|
|
|
|
Status = set_end_of_file_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.AdvanceOnly, false);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileLinkInformation:
|
|
TRACE("FileLinkInformation\n");
|
|
Status = set_link_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.FileObject, false);
|
|
break;
|
|
|
|
case FilePositionInformation:
|
|
TRACE("FilePositionInformation\n");
|
|
Status = set_position_information(IrpSp->FileObject, Irp);
|
|
break;
|
|
|
|
case FileRenameInformation:
|
|
TRACE("FileRenameInformation\n");
|
|
Status = set_rename_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.FileObject, false);
|
|
break;
|
|
|
|
case FileValidDataLengthInformation:
|
|
{
|
|
TRACE("FileValidDataLengthInformation\n");
|
|
|
|
if (Irp->RequestorMode == UserMode && !(ccb->access & (FILE_WRITE_DATA | FILE_APPEND_DATA))) {
|
|
WARN("insufficient privileges\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
break;
|
|
}
|
|
|
|
Status = set_valid_data_length_information(Vcb, Irp, IrpSp->FileObject);
|
|
|
|
break;
|
|
}
|
|
|
|
#ifndef __REACTOS__
|
|
#ifndef _MSC_VER
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wswitch"
|
|
#endif
|
|
case FileDispositionInformationEx:
|
|
{
|
|
TRACE("FileDispositionInformationEx\n");
|
|
|
|
if (Irp->RequestorMode == UserMode && !(ccb->access & DELETE)) {
|
|
WARN("insufficient privileges\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
break;
|
|
}
|
|
|
|
Status = set_disposition_information(Vcb, Irp, IrpSp->FileObject, true);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileRenameInformationEx:
|
|
TRACE("FileRenameInformationEx\n");
|
|
Status = set_rename_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.FileObject, true);
|
|
break;
|
|
|
|
case FileLinkInformationEx:
|
|
TRACE("FileLinkInformationEx\n");
|
|
Status = set_link_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.FileObject, true);
|
|
break;
|
|
|
|
case FileCaseSensitiveInformation:
|
|
TRACE("FileCaseSensitiveInformation\n");
|
|
|
|
if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_ATTRIBUTES)) {
|
|
WARN("insufficient privileges\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
break;
|
|
}
|
|
|
|
Status = set_case_sensitive_information(Irp);
|
|
break;
|
|
#ifndef _MSC_VER
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
#endif
|
|
|
|
default:
|
|
WARN("unknown FileInformationClass %u\n", IrpSp->Parameters.SetFile.FileInformationClass);
|
|
}
|
|
|
|
end:
|
|
Irp->IoStatus.Status = Status;
|
|
|
|
TRACE("returning %08x\n", Status);
|
|
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
|
|
if (top_level)
|
|
IoSetTopLevelIrp(NULL);
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS fill_in_file_basic_information(FILE_BASIC_INFORMATION* fbi, INODE_ITEM* ii, LONG* length, fcb* fcb, file_ref* fileref) {
|
|
RtlZeroMemory(fbi, sizeof(FILE_BASIC_INFORMATION));
|
|
|
|
*length -= sizeof(FILE_BASIC_INFORMATION);
|
|
|
|
if (fcb == fcb->Vcb->dummy_fcb) {
|
|
LARGE_INTEGER time;
|
|
|
|
KeQuerySystemTime(&time);
|
|
fbi->CreationTime = fbi->LastAccessTime = fbi->LastWriteTime = fbi->ChangeTime = time;
|
|
} else {
|
|
fbi->CreationTime.QuadPart = unix_time_to_win(&ii->otime);
|
|
fbi->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime);
|
|
fbi->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime);
|
|
fbi->ChangeTime.QuadPart = unix_time_to_win(&ii->st_ctime);
|
|
}
|
|
|
|
if (fcb->ads) {
|
|
if (!fileref || !fileref->parent) {
|
|
ERR("no fileref for stream\n");
|
|
return STATUS_INTERNAL_ERROR;
|
|
} else
|
|
fbi->FileAttributes = fileref->parent->fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fileref->parent->fcb->atts;
|
|
} else
|
|
fbi->FileAttributes = fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fcb->atts;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS fill_in_file_network_open_information(FILE_NETWORK_OPEN_INFORMATION* fnoi, fcb* fcb, file_ref* fileref, LONG* length) {
|
|
INODE_ITEM* ii;
|
|
|
|
if (*length < (LONG)sizeof(FILE_NETWORK_OPEN_INFORMATION)) {
|
|
WARN("overflow\n");
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
RtlZeroMemory(fnoi, sizeof(FILE_NETWORK_OPEN_INFORMATION));
|
|
|
|
*length -= sizeof(FILE_NETWORK_OPEN_INFORMATION);
|
|
|
|
if (fcb->ads) {
|
|
if (!fileref || !fileref->parent) {
|
|
ERR("no fileref for stream\n");
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
ii = &fileref->parent->fcb->inode_item;
|
|
} else
|
|
ii = &fcb->inode_item;
|
|
|
|
if (fcb == fcb->Vcb->dummy_fcb) {
|
|
LARGE_INTEGER time;
|
|
|
|
KeQuerySystemTime(&time);
|
|
fnoi->CreationTime = fnoi->LastAccessTime = fnoi->LastWriteTime = fnoi->ChangeTime = time;
|
|
} else {
|
|
fnoi->CreationTime.QuadPart = unix_time_to_win(&ii->otime);
|
|
fnoi->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime);
|
|
fnoi->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime);
|
|
fnoi->ChangeTime.QuadPart = unix_time_to_win(&ii->st_ctime);
|
|
}
|
|
|
|
if (fcb->ads) {
|
|
fnoi->AllocationSize.QuadPart = fnoi->EndOfFile.QuadPart = fcb->adsdata.Length;
|
|
fnoi->FileAttributes = fileref->parent->fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fileref->parent->fcb->atts;
|
|
} else {
|
|
fnoi->AllocationSize.QuadPart = fcb_alloc_size(fcb);
|
|
fnoi->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size;
|
|
fnoi->FileAttributes = fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fcb->atts;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS fill_in_file_standard_information(FILE_STANDARD_INFORMATION* fsi, fcb* fcb, file_ref* fileref, LONG* length) {
|
|
RtlZeroMemory(fsi, sizeof(FILE_STANDARD_INFORMATION));
|
|
|
|
*length -= sizeof(FILE_STANDARD_INFORMATION);
|
|
|
|
if (fcb->ads) {
|
|
if (!fileref || !fileref->parent) {
|
|
ERR("no fileref for stream\n");
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
fsi->AllocationSize.QuadPart = fsi->EndOfFile.QuadPart = fcb->adsdata.Length;
|
|
fsi->NumberOfLinks = fileref->parent->fcb->inode_item.st_nlink;
|
|
fsi->Directory = false;
|
|
} else {
|
|
fsi->AllocationSize.QuadPart = fcb_alloc_size(fcb);
|
|
fsi->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size;
|
|
fsi->NumberOfLinks = fcb->inode_item.st_nlink;
|
|
fsi->Directory = S_ISDIR(fcb->inode_item.st_mode);
|
|
}
|
|
|
|
TRACE("length = %I64u\n", fsi->EndOfFile.QuadPart);
|
|
|
|
fsi->DeletePending = fileref ? fileref->delete_on_close : false;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS fill_in_file_internal_information(FILE_INTERNAL_INFORMATION* fii, fcb* fcb, LONG* length) {
|
|
*length -= sizeof(FILE_INTERNAL_INFORMATION);
|
|
|
|
fii->IndexNumber.QuadPart = make_file_id(fcb->subvol, fcb->inode);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS fill_in_file_ea_information(FILE_EA_INFORMATION* eai, fcb* fcb, LONG* length) {
|
|
*length -= sizeof(FILE_EA_INFORMATION);
|
|
|
|
/* This value appears to be the size of the structure NTFS stores on disk, and not,
|
|
* as might be expected, the size of FILE_FULL_EA_INFORMATION (which is what we store).
|
|
* The formula is 4 bytes as a header, followed by 5 + NameLength + ValueLength for each
|
|
* item. */
|
|
|
|
eai->EaSize = fcb->ealen;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS fill_in_file_position_information(FILE_POSITION_INFORMATION* fpi, PFILE_OBJECT FileObject, LONG* length) {
|
|
RtlZeroMemory(fpi, sizeof(FILE_POSITION_INFORMATION));
|
|
|
|
*length -= sizeof(FILE_POSITION_INFORMATION);
|
|
|
|
fpi->CurrentByteOffset = FileObject->CurrentByteOffset;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS fileref_get_filename(file_ref* fileref, PUNICODE_STRING fn, USHORT* name_offset, ULONG* preqlen) {
|
|
file_ref* fr;
|
|
NTSTATUS Status;
|
|
ULONG reqlen = 0;
|
|
USHORT offset;
|
|
bool overflow = false;
|
|
|
|
// FIXME - we need a lock on filerefs' filepart
|
|
|
|
if (fileref == fileref->fcb->Vcb->root_fileref) {
|
|
if (fn->MaximumLength >= sizeof(WCHAR)) {
|
|
fn->Buffer[0] = '\\';
|
|
fn->Length = sizeof(WCHAR);
|
|
|
|
if (name_offset)
|
|
*name_offset = 0;
|
|
|
|
return STATUS_SUCCESS;
|
|
} else {
|
|
if (preqlen)
|
|
*preqlen = sizeof(WCHAR);
|
|
fn->Length = 0;
|
|
|
|
return STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
}
|
|
|
|
fr = fileref;
|
|
offset = 0;
|
|
|
|
while (fr->parent) {
|
|
USHORT movelen;
|
|
|
|
if (!fr->dc)
|
|
return STATUS_INTERNAL_ERROR;
|
|
|
|
if (!overflow) {
|
|
if (fr->dc->name.Length + sizeof(WCHAR) + fn->Length > fn->MaximumLength)
|
|
overflow = true;
|
|
}
|
|
|
|
if (overflow)
|
|
movelen = fn->MaximumLength - fr->dc->name.Length - sizeof(WCHAR);
|
|
else
|
|
movelen = fn->Length;
|
|
|
|
if ((!overflow || fn->MaximumLength > fr->dc->name.Length + sizeof(WCHAR)) && movelen > 0) {
|
|
RtlMoveMemory(&fn->Buffer[(fr->dc->name.Length / sizeof(WCHAR)) + 1], fn->Buffer, movelen);
|
|
offset += fr->dc->name.Length + sizeof(WCHAR);
|
|
}
|
|
|
|
if (fn->MaximumLength >= sizeof(WCHAR)) {
|
|
fn->Buffer[0] = fr->fcb->ads ? ':' : '\\';
|
|
fn->Length += sizeof(WCHAR);
|
|
|
|
if (fn->MaximumLength > sizeof(WCHAR)) {
|
|
RtlCopyMemory(&fn->Buffer[1], fr->dc->name.Buffer, min(fr->dc->name.Length, fn->MaximumLength - sizeof(WCHAR)));
|
|
fn->Length += fr->dc->name.Length;
|
|
}
|
|
|
|
if (fn->Length > fn->MaximumLength) {
|
|
fn->Length = fn->MaximumLength;
|
|
overflow = true;
|
|
}
|
|
}
|
|
|
|
reqlen += sizeof(WCHAR) + fr->dc->name.Length;
|
|
|
|
fr = fr->parent;
|
|
}
|
|
|
|
offset += sizeof(WCHAR);
|
|
|
|
if (overflow) {
|
|
if (preqlen)
|
|
*preqlen = reqlen;
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
} else {
|
|
if (name_offset)
|
|
*name_offset = offset;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS fill_in_file_name_information(FILE_NAME_INFORMATION* fni, fcb* fcb, file_ref* fileref, LONG* length) {
|
|
ULONG reqlen;
|
|
UNICODE_STRING fn;
|
|
NTSTATUS Status;
|
|
static const WCHAR datasuf[] = {':','$','D','A','T','A',0};
|
|
uint16_t datasuflen = sizeof(datasuf) - sizeof(WCHAR);
|
|
|
|
if (!fileref) {
|
|
ERR("called without fileref\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
*length -= (LONG)offsetof(FILE_NAME_INFORMATION, FileName[0]);
|
|
|
|
TRACE("maximum length is %u\n", *length);
|
|
fni->FileNameLength = 0;
|
|
|
|
fni->FileName[0] = 0;
|
|
|
|
fn.Buffer = fni->FileName;
|
|
fn.Length = 0;
|
|
fn.MaximumLength = (uint16_t)*length;
|
|
|
|
Status = fileref_get_filename(fileref, &fn, NULL, &reqlen);
|
|
if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) {
|
|
ERR("fileref_get_filename returned %08x\n", Status);
|
|
return Status;
|
|
}
|
|
|
|
if (fcb->ads) {
|
|
if (Status == STATUS_BUFFER_OVERFLOW)
|
|
reqlen += datasuflen;
|
|
else {
|
|
if (fn.Length + datasuflen > fn.MaximumLength) {
|
|
RtlCopyMemory(&fn.Buffer[fn.Length / sizeof(WCHAR)], datasuf, fn.MaximumLength - fn.Length);
|
|
reqlen += datasuflen;
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
} else {
|
|
RtlCopyMemory(&fn.Buffer[fn.Length / sizeof(WCHAR)], datasuf, datasuflen);
|
|
fn.Length += datasuflen;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Status == STATUS_BUFFER_OVERFLOW) {
|
|
*length = -1;
|
|
fni->FileNameLength = reqlen;
|
|
TRACE("%.*S (truncated)\n", fn.Length / sizeof(WCHAR), fn.Buffer);
|
|
} else {
|
|
*length -= fn.Length;
|
|
fni->FileNameLength = fn.Length;
|
|
TRACE("%.*S\n", fn.Length / sizeof(WCHAR), fn.Buffer);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS fill_in_file_attribute_information(FILE_ATTRIBUTE_TAG_INFORMATION* ati, fcb* fcb, ccb* ccb, LONG* length) {
|
|
*length -= sizeof(FILE_ATTRIBUTE_TAG_INFORMATION);
|
|
|
|
if (fcb->ads) {
|
|
if (!ccb->fileref || !ccb->fileref->parent) {
|
|
ERR("no fileref for stream\n");
|
|
return STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
ati->FileAttributes = ccb->fileref->parent->fcb->atts;
|
|
} else
|
|
ati->FileAttributes = fcb->atts;
|
|
|
|
if (!(ati->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
|
|
ati->ReparseTag = 0;
|
|
else
|
|
ati->ReparseTag = get_reparse_tag_fcb(fcb);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS fill_in_file_stream_information(FILE_STREAM_INFORMATION* fsi, file_ref* fileref, LONG* length) {
|
|
LONG reqsize;
|
|
LIST_ENTRY* le;
|
|
FILE_STREAM_INFORMATION *entry, *lastentry;
|
|
NTSTATUS Status;
|
|
|
|
static const WCHAR datasuf[] = L":$DATA";
|
|
UNICODE_STRING suf;
|
|
|
|
if (!fileref) {
|
|
ERR("fileref was NULL\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
suf.Buffer = (WCHAR*)datasuf;
|
|
suf.Length = suf.MaximumLength = sizeof(datasuf) - sizeof(WCHAR);
|
|
|
|
if (fileref->fcb->type != BTRFS_TYPE_DIRECTORY)
|
|
reqsize = sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR);
|
|
else
|
|
reqsize = 0;
|
|
|
|
ExAcquireResourceSharedLite(&fileref->fcb->nonpaged->dir_children_lock, true);
|
|
|
|
le = fileref->fcb->dir_children_index.Flink;
|
|
while (le != &fileref->fcb->dir_children_index) {
|
|
dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_index);
|
|
|
|
if (dc->index == 0) {
|
|
reqsize = (ULONG)sector_align(reqsize, sizeof(LONGLONG));
|
|
reqsize += sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR) + dc->name.Length;
|
|
} else
|
|
break;
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
TRACE("length = %i, reqsize = %u\n", *length, reqsize);
|
|
|
|
if (reqsize > *length) {
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
goto end;
|
|
}
|
|
|
|
entry = fsi;
|
|
lastentry = NULL;
|
|
|
|
if (fileref->fcb->type != BTRFS_TYPE_DIRECTORY) {
|
|
ULONG off;
|
|
|
|
entry->NextEntryOffset = 0;
|
|
entry->StreamNameLength = suf.Length + sizeof(WCHAR);
|
|
entry->StreamSize.QuadPart = fileref->fcb->inode_item.st_size;
|
|
entry->StreamAllocationSize.QuadPart = fcb_alloc_size(fileref->fcb);
|
|
|
|
entry->StreamName[0] = ':';
|
|
RtlCopyMemory(&entry->StreamName[1], suf.Buffer, suf.Length);
|
|
|
|
off = (ULONG)sector_align(sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR), sizeof(LONGLONG));
|
|
|
|
lastentry = entry;
|
|
entry = (FILE_STREAM_INFORMATION*)((uint8_t*)entry + off);
|
|
}
|
|
|
|
le = fileref->fcb->dir_children_index.Flink;
|
|
while (le != &fileref->fcb->dir_children_index) {
|
|
dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_index);
|
|
|
|
if (dc->index == 0) {
|
|
ULONG off;
|
|
|
|
entry->NextEntryOffset = 0;
|
|
entry->StreamNameLength = dc->name.Length + suf.Length + sizeof(WCHAR);
|
|
|
|
if (dc->fileref)
|
|
entry->StreamSize.QuadPart = dc->fileref->fcb->adsdata.Length;
|
|
else
|
|
entry->StreamSize.QuadPart = dc->size;
|
|
|
|
entry->StreamAllocationSize.QuadPart = entry->StreamSize.QuadPart;
|
|
|
|
entry->StreamName[0] = ':';
|
|
|
|
RtlCopyMemory(&entry->StreamName[1], dc->name.Buffer, dc->name.Length);
|
|
RtlCopyMemory(&entry->StreamName[1 + (dc->name.Length / sizeof(WCHAR))], suf.Buffer, suf.Length);
|
|
|
|
if (lastentry)
|
|
lastentry->NextEntryOffset = (uint32_t)((uint8_t*)entry - (uint8_t*)lastentry);
|
|
|
|
off = (ULONG)sector_align(sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR) + dc->name.Length, sizeof(LONGLONG));
|
|
|
|
lastentry = entry;
|
|
entry = (FILE_STREAM_INFORMATION*)((uint8_t*)entry + off);
|
|
} else
|
|
break;
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
*length -= reqsize;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
end:
|
|
ExReleaseResourceLite(&fileref->fcb->nonpaged->dir_children_lock);
|
|
|
|
return Status;
|
|
}
|
|
|
|
#ifndef __REACTOS__
|
|
static NTSTATUS fill_in_file_standard_link_information(FILE_STANDARD_LINK_INFORMATION* fsli, fcb* fcb, file_ref* fileref, LONG* length) {
|
|
TRACE("FileStandardLinkInformation\n");
|
|
|
|
// FIXME - NumberOfAccessibleLinks should subtract open links which have been marked as delete_on_close
|
|
|
|
fsli->NumberOfAccessibleLinks = fcb->inode_item.st_nlink;
|
|
fsli->TotalNumberOfLinks = fcb->inode_item.st_nlink;
|
|
fsli->DeletePending = fileref ? fileref->delete_on_close : false;
|
|
fsli->Directory = (!fcb->ads && fcb->type == BTRFS_TYPE_DIRECTORY) ? true : false;
|
|
|
|
*length -= sizeof(FILE_STANDARD_LINK_INFORMATION);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS fill_in_hard_link_information(FILE_LINKS_INFORMATION* fli, file_ref* fileref, PIRP Irp, LONG* length) {
|
|
NTSTATUS Status;
|
|
LIST_ENTRY* le;
|
|
LONG bytes_needed;
|
|
FILE_LINK_ENTRY_INFORMATION* feli;
|
|
bool overflow = false;
|
|
fcb* fcb = fileref->fcb;
|
|
ULONG len;
|
|
|
|
if (fcb->ads)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
if (*length < (LONG)offsetof(FILE_LINKS_INFORMATION, Entry))
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
RtlZeroMemory(fli, *length);
|
|
|
|
bytes_needed = offsetof(FILE_LINKS_INFORMATION, Entry);
|
|
len = bytes_needed;
|
|
feli = NULL;
|
|
|
|
ExAcquireResourceSharedLite(fcb->Header.Resource, true);
|
|
|
|
if (fcb->inode == SUBVOL_ROOT_INODE) {
|
|
ULONG namelen;
|
|
|
|
if (fcb == fcb->Vcb->root_fileref->fcb)
|
|
namelen = sizeof(WCHAR);
|
|
else
|
|
namelen = fileref->dc->name.Length;
|
|
|
|
bytes_needed += sizeof(FILE_LINK_ENTRY_INFORMATION) - sizeof(WCHAR) + namelen;
|
|
|
|
if (bytes_needed > *length)
|
|
overflow = true;
|
|
|
|
if (!overflow) {
|
|
feli = &fli->Entry;
|
|
|
|
feli->NextEntryOffset = 0;
|
|
feli->ParentFileId = 0; // we use an inode of 0 to mean the parent of a subvolume
|
|
|
|
if (fcb == fcb->Vcb->root_fileref->fcb) {
|
|
feli->FileNameLength = 1;
|
|
feli->FileName[0] = '.';
|
|
} else {
|
|
feli->FileNameLength = fileref->dc->name.Length / sizeof(WCHAR);
|
|
RtlCopyMemory(feli->FileName, fileref->dc->name.Buffer, fileref->dc->name.Length);
|
|
}
|
|
|
|
fli->EntriesReturned++;
|
|
|
|
len = bytes_needed;
|
|
}
|
|
} else {
|
|
ExAcquireResourceExclusiveLite(&fcb->Vcb->fileref_lock, true);
|
|
|
|
if (IsListEmpty(&fcb->hardlinks)) {
|
|
bytes_needed += sizeof(FILE_LINK_ENTRY_INFORMATION) + fileref->dc->name.Length - sizeof(WCHAR);
|
|
|
|
if (bytes_needed > *length)
|
|
overflow = true;
|
|
|
|
if (!overflow) {
|
|
feli = &fli->Entry;
|
|
|
|
feli->NextEntryOffset = 0;
|
|
feli->ParentFileId = fileref->parent->fcb->inode;
|
|
feli->FileNameLength = fileref->dc->name.Length / sizeof(WCHAR);
|
|
RtlCopyMemory(feli->FileName, fileref->dc->name.Buffer, fileref->dc->name.Length);
|
|
|
|
fli->EntriesReturned++;
|
|
|
|
len = bytes_needed;
|
|
}
|
|
} else {
|
|
le = fcb->hardlinks.Flink;
|
|
while (le != &fcb->hardlinks) {
|
|
hardlink* hl = CONTAINING_RECORD(le, hardlink, list_entry);
|
|
file_ref* parfr;
|
|
|
|
TRACE("parent %I64x, index %I64x, name %.*S\n", hl->parent, hl->index, hl->name.Length / sizeof(WCHAR), hl->name.Buffer);
|
|
|
|
Status = open_fileref_by_inode(fcb->Vcb, fcb->subvol, hl->parent, &parfr, Irp);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("open_fileref_by_inode returned %08x\n", Status);
|
|
} else if (!parfr->deleted) {
|
|
LIST_ENTRY* le2;
|
|
bool found = false, deleted = false;
|
|
UNICODE_STRING* fn = NULL;
|
|
|
|
le2 = parfr->children.Flink;
|
|
while (le2 != &parfr->children) {
|
|
file_ref* fr2 = CONTAINING_RECORD(le2, file_ref, list_entry);
|
|
|
|
if (fr2->dc->index == hl->index) {
|
|
found = true;
|
|
deleted = fr2->deleted;
|
|
|
|
if (!deleted)
|
|
fn = &fr2->dc->name;
|
|
|
|
break;
|
|
}
|
|
|
|
le2 = le2->Flink;
|
|
}
|
|
|
|
if (!found)
|
|
fn = &hl->name;
|
|
|
|
if (!deleted) {
|
|
TRACE("fn = %.*S (found = %u)\n", fn->Length / sizeof(WCHAR), fn->Buffer, found);
|
|
|
|
if (feli)
|
|
bytes_needed = (LONG)sector_align(bytes_needed, 8);
|
|
|
|
bytes_needed += sizeof(FILE_LINK_ENTRY_INFORMATION) + fn->Length - sizeof(WCHAR);
|
|
|
|
if (bytes_needed > *length)
|
|
overflow = true;
|
|
|
|
if (!overflow) {
|
|
if (feli) {
|
|
feli->NextEntryOffset = (ULONG)sector_align(sizeof(FILE_LINK_ENTRY_INFORMATION) + ((feli->FileNameLength - 1) * sizeof(WCHAR)), 8);
|
|
feli = (FILE_LINK_ENTRY_INFORMATION*)((uint8_t*)feli + feli->NextEntryOffset);
|
|
} else
|
|
feli = &fli->Entry;
|
|
|
|
feli->NextEntryOffset = 0;
|
|
feli->ParentFileId = parfr->fcb->inode;
|
|
feli->FileNameLength = fn->Length / sizeof(WCHAR);
|
|
RtlCopyMemory(feli->FileName, fn->Buffer, fn->Length);
|
|
|
|
fli->EntriesReturned++;
|
|
|
|
len = bytes_needed;
|
|
}
|
|
}
|
|
|
|
free_fileref(parfr);
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
}
|
|
|
|
ExReleaseResourceLite(&fcb->Vcb->fileref_lock);
|
|
}
|
|
|
|
fli->BytesNeeded = bytes_needed;
|
|
|
|
*length -= len;
|
|
|
|
Status = overflow ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS;
|
|
|
|
ExReleaseResourceLite(fcb->Header.Resource);
|
|
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS fill_in_hard_link_full_id_information(FILE_LINKS_FULL_ID_INFORMATION* flfii, file_ref* fileref, PIRP Irp, LONG* length) {
|
|
NTSTATUS Status;
|
|
LIST_ENTRY* le;
|
|
LONG bytes_needed;
|
|
FILE_LINK_ENTRY_FULL_ID_INFORMATION* flefii;
|
|
bool overflow = false;
|
|
fcb* fcb = fileref->fcb;
|
|
ULONG len;
|
|
|
|
if (fcb->ads)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
if (*length < (LONG)offsetof(FILE_LINKS_FULL_ID_INFORMATION, Entry))
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
RtlZeroMemory(flfii, *length);
|
|
|
|
bytes_needed = offsetof(FILE_LINKS_FULL_ID_INFORMATION, Entry);
|
|
len = bytes_needed;
|
|
flefii = NULL;
|
|
|
|
ExAcquireResourceSharedLite(fcb->Header.Resource, true);
|
|
|
|
if (fcb->inode == SUBVOL_ROOT_INODE) {
|
|
ULONG namelen;
|
|
|
|
if (fcb == fcb->Vcb->root_fileref->fcb)
|
|
namelen = sizeof(WCHAR);
|
|
else
|
|
namelen = fileref->dc->name.Length;
|
|
|
|
bytes_needed += offsetof(FILE_LINK_ENTRY_FULL_ID_INFORMATION, FileName[0]) + namelen;
|
|
|
|
if (bytes_needed > *length)
|
|
overflow = true;
|
|
|
|
if (!overflow) {
|
|
flefii = &flfii->Entry;
|
|
|
|
flefii->NextEntryOffset = 0;
|
|
|
|
if (fcb == fcb->Vcb->root_fileref->fcb) {
|
|
RtlZeroMemory(&flefii->ParentFileId.Identifier[0], sizeof(FILE_ID_128));
|
|
flefii->FileNameLength = 1;
|
|
flefii->FileName[0] = '.';
|
|
} else {
|
|
RtlCopyMemory(&flefii->ParentFileId.Identifier[0], &fileref->parent->fcb->inode, sizeof(uint64_t));
|
|
RtlCopyMemory(&flefii->ParentFileId.Identifier[sizeof(uint64_t)], &fileref->parent->fcb->subvol->id, sizeof(uint64_t));
|
|
|
|
flefii->FileNameLength = fileref->dc->name.Length / sizeof(WCHAR);
|
|
RtlCopyMemory(flefii->FileName, fileref->dc->name.Buffer, fileref->dc->name.Length);
|
|
}
|
|
|
|
flfii->EntriesReturned++;
|
|
|
|
len = bytes_needed;
|
|
}
|
|
} else {
|
|
ExAcquireResourceExclusiveLite(&fcb->Vcb->fileref_lock, true);
|
|
|
|
if (IsListEmpty(&fcb->hardlinks)) {
|
|
bytes_needed += offsetof(FILE_LINK_ENTRY_FULL_ID_INFORMATION, FileName[0]) + fileref->dc->name.Length;
|
|
|
|
if (bytes_needed > *length)
|
|
overflow = true;
|
|
|
|
if (!overflow) {
|
|
flefii = &flfii->Entry;
|
|
|
|
flefii->NextEntryOffset = 0;
|
|
|
|
RtlCopyMemory(&flefii->ParentFileId.Identifier[0], &fileref->parent->fcb->inode, sizeof(uint64_t));
|
|
RtlCopyMemory(&flefii->ParentFileId.Identifier[sizeof(uint64_t)], &fileref->parent->fcb->subvol->id, sizeof(uint64_t));
|
|
|
|
flefii->FileNameLength = fileref->dc->name.Length / sizeof(WCHAR);
|
|
RtlCopyMemory(flefii->FileName, fileref->dc->name.Buffer, fileref->dc->name.Length);
|
|
|
|
flfii->EntriesReturned++;
|
|
|
|
len = bytes_needed;
|
|
}
|
|
} else {
|
|
le = fcb->hardlinks.Flink;
|
|
while (le != &fcb->hardlinks) {
|
|
hardlink* hl = CONTAINING_RECORD(le, hardlink, list_entry);
|
|
file_ref* parfr;
|
|
|
|
TRACE("parent %I64x, index %I64x, name %.*S\n", hl->parent, hl->index, hl->name.Length / sizeof(WCHAR), hl->name.Buffer);
|
|
|
|
Status = open_fileref_by_inode(fcb->Vcb, fcb->subvol, hl->parent, &parfr, Irp);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("open_fileref_by_inode returned %08x\n", Status);
|
|
} else if (!parfr->deleted) {
|
|
LIST_ENTRY* le2;
|
|
bool found = false, deleted = false;
|
|
UNICODE_STRING* fn = NULL;
|
|
|
|
le2 = parfr->children.Flink;
|
|
while (le2 != &parfr->children) {
|
|
file_ref* fr2 = CONTAINING_RECORD(le2, file_ref, list_entry);
|
|
|
|
if (fr2->dc->index == hl->index) {
|
|
found = true;
|
|
deleted = fr2->deleted;
|
|
|
|
if (!deleted)
|
|
fn = &fr2->dc->name;
|
|
|
|
break;
|
|
}
|
|
|
|
le2 = le2->Flink;
|
|
}
|
|
|
|
if (!found)
|
|
fn = &hl->name;
|
|
|
|
if (!deleted) {
|
|
TRACE("fn = %.*S (found = %u)\n", fn->Length / sizeof(WCHAR), fn->Buffer, found);
|
|
|
|
if (flefii)
|
|
bytes_needed = (LONG)sector_align(bytes_needed, 8);
|
|
|
|
bytes_needed += offsetof(FILE_LINK_ENTRY_FULL_ID_INFORMATION, FileName[0]) + fn->Length;
|
|
|
|
if (bytes_needed > *length)
|
|
overflow = true;
|
|
|
|
if (!overflow) {
|
|
if (flefii) {
|
|
flefii->NextEntryOffset = (ULONG)sector_align(offsetof(FILE_LINK_ENTRY_FULL_ID_INFORMATION, FileName[0]) + (flefii->FileNameLength * sizeof(WCHAR)), 8);
|
|
flefii = (FILE_LINK_ENTRY_FULL_ID_INFORMATION*)((uint8_t*)flefii + flefii->NextEntryOffset);
|
|
} else
|
|
flefii = &flfii->Entry;
|
|
|
|
flefii->NextEntryOffset = 0;
|
|
|
|
RtlCopyMemory(&flefii->ParentFileId.Identifier[0], &parfr->fcb->inode, sizeof(uint64_t));
|
|
RtlCopyMemory(&flefii->ParentFileId.Identifier[sizeof(uint64_t)], &parfr->fcb->subvol->id, sizeof(uint64_t));
|
|
|
|
flefii->FileNameLength = fn->Length / sizeof(WCHAR);
|
|
RtlCopyMemory(flefii->FileName, fn->Buffer, fn->Length);
|
|
|
|
flfii->EntriesReturned++;
|
|
|
|
len = bytes_needed;
|
|
}
|
|
}
|
|
|
|
free_fileref(parfr);
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
}
|
|
|
|
ExReleaseResourceLite(&fcb->Vcb->fileref_lock);
|
|
}
|
|
|
|
flfii->BytesNeeded = bytes_needed;
|
|
|
|
*length -= len;
|
|
|
|
Status = overflow ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS;
|
|
|
|
ExReleaseResourceLite(fcb->Header.Resource);
|
|
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS fill_in_file_id_information(FILE_ID_INFORMATION* fii, fcb* fcb, LONG* length) {
|
|
RtlCopyMemory(&fii->VolumeSerialNumber, &fcb->Vcb->superblock.uuid.uuid[8], sizeof(uint64_t));
|
|
RtlCopyMemory(&fii->FileId.Identifier[0], &fcb->inode, sizeof(uint64_t));
|
|
RtlCopyMemory(&fii->FileId.Identifier[sizeof(uint64_t)], &fcb->subvol->id, sizeof(uint64_t));
|
|
|
|
*length -= sizeof(FILE_ID_INFORMATION);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS fill_in_file_stat_information(FILE_STAT_INFORMATION* fsi, fcb* fcb, ccb* ccb, LONG* length) {
|
|
INODE_ITEM* ii;
|
|
|
|
fsi->FileId.LowPart = (uint32_t)fcb->inode;
|
|
fsi->FileId.HighPart = (uint32_t)fcb->subvol->id;
|
|
|
|
if (fcb->ads)
|
|
ii = &ccb->fileref->parent->fcb->inode_item;
|
|
else
|
|
ii = &fcb->inode_item;
|
|
|
|
if (fcb == fcb->Vcb->dummy_fcb) {
|
|
LARGE_INTEGER time;
|
|
|
|
KeQuerySystemTime(&time);
|
|
fsi->CreationTime = fsi->LastAccessTime = fsi->LastWriteTime = fsi->ChangeTime = time;
|
|
} else {
|
|
fsi->CreationTime.QuadPart = unix_time_to_win(&ii->otime);
|
|
fsi->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime);
|
|
fsi->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime);
|
|
fsi->ChangeTime.QuadPart = unix_time_to_win(&ii->st_ctime);
|
|
}
|
|
|
|
if (fcb->ads) {
|
|
fsi->AllocationSize.QuadPart = fsi->EndOfFile.QuadPart = fcb->adsdata.Length;
|
|
fsi->FileAttributes = ccb->fileref->parent->fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : ccb->fileref->parent->fcb->atts;
|
|
} else {
|
|
fsi->AllocationSize.QuadPart = fcb_alloc_size(fcb);
|
|
fsi->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size;
|
|
fsi->FileAttributes = fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fcb->atts;
|
|
}
|
|
|
|
if (fcb->type == BTRFS_TYPE_SOCKET)
|
|
fsi->ReparseTag = IO_REPARSE_TAG_LXSS_SOCKET;
|
|
else if (fcb->type == BTRFS_TYPE_FIFO)
|
|
fsi->ReparseTag = IO_REPARSE_TAG_LXSS_FIFO;
|
|
else if (fcb->type == BTRFS_TYPE_CHARDEV)
|
|
fsi->ReparseTag = IO_REPARSE_TAG_LXSS_CHARDEV;
|
|
else if (fcb->type == BTRFS_TYPE_BLOCKDEV)
|
|
fsi->ReparseTag = IO_REPARSE_TAG_LXSS_BLOCKDEV;
|
|
else if (!(fsi->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
|
|
fsi->ReparseTag = 0;
|
|
else
|
|
fsi->ReparseTag = get_reparse_tag_fcb(fcb);
|
|
|
|
if (fcb->type == BTRFS_TYPE_SOCKET || fcb->type == BTRFS_TYPE_FIFO || fcb->type == BTRFS_TYPE_CHARDEV || fcb->type == BTRFS_TYPE_BLOCKDEV)
|
|
fsi->FileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT;
|
|
|
|
if (fcb->ads)
|
|
fsi->NumberOfLinks = ccb->fileref->parent->fcb->inode_item.st_nlink;
|
|
else
|
|
fsi->NumberOfLinks = fcb->inode_item.st_nlink;
|
|
|
|
fsi->EffectiveAccess = ccb->access;
|
|
|
|
*length -= sizeof(FILE_STAT_INFORMATION);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS fill_in_file_stat_lx_information(FILE_STAT_LX_INFORMATION* fsli, fcb* fcb, ccb* ccb, LONG* length) {
|
|
INODE_ITEM* ii;
|
|
|
|
fsli->FileId.LowPart = (uint32_t)fcb->inode;
|
|
fsli->FileId.HighPart = (uint32_t)fcb->subvol->id;
|
|
|
|
if (fcb->ads)
|
|
ii = &ccb->fileref->parent->fcb->inode_item;
|
|
else
|
|
ii = &fcb->inode_item;
|
|
|
|
if (fcb == fcb->Vcb->dummy_fcb) {
|
|
LARGE_INTEGER time;
|
|
|
|
KeQuerySystemTime(&time);
|
|
fsli->CreationTime = fsli->LastAccessTime = fsli->LastWriteTime = fsli->ChangeTime = time;
|
|
} else {
|
|
fsli->CreationTime.QuadPart = unix_time_to_win(&ii->otime);
|
|
fsli->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime);
|
|
fsli->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime);
|
|
fsli->ChangeTime.QuadPart = unix_time_to_win(&ii->st_ctime);
|
|
}
|
|
|
|
if (fcb->ads) {
|
|
fsli->AllocationSize.QuadPart = fsli->EndOfFile.QuadPart = fcb->adsdata.Length;
|
|
fsli->FileAttributes = ccb->fileref->parent->fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : ccb->fileref->parent->fcb->atts;
|
|
} else {
|
|
fsli->AllocationSize.QuadPart = fcb_alloc_size(fcb);
|
|
fsli->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size;
|
|
fsli->FileAttributes = fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fcb->atts;
|
|
}
|
|
|
|
if (fcb->type == BTRFS_TYPE_SOCKET)
|
|
fsli->ReparseTag = IO_REPARSE_TAG_LXSS_SOCKET;
|
|
else if (fcb->type == BTRFS_TYPE_FIFO)
|
|
fsli->ReparseTag = IO_REPARSE_TAG_LXSS_FIFO;
|
|
else if (fcb->type == BTRFS_TYPE_CHARDEV)
|
|
fsli->ReparseTag = IO_REPARSE_TAG_LXSS_CHARDEV;
|
|
else if (fcb->type == BTRFS_TYPE_BLOCKDEV)
|
|
fsli->ReparseTag = IO_REPARSE_TAG_LXSS_BLOCKDEV;
|
|
else if (!(fsli->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
|
|
fsli->ReparseTag = 0;
|
|
else
|
|
fsli->ReparseTag = get_reparse_tag_fcb(fcb);
|
|
|
|
if (fcb->type == BTRFS_TYPE_SOCKET || fcb->type == BTRFS_TYPE_FIFO || fcb->type == BTRFS_TYPE_CHARDEV || fcb->type == BTRFS_TYPE_BLOCKDEV)
|
|
fsli->FileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT;
|
|
|
|
if (fcb->ads)
|
|
fsli->NumberOfLinks = ccb->fileref->parent->fcb->inode_item.st_nlink;
|
|
else
|
|
fsli->NumberOfLinks = fcb->inode_item.st_nlink;
|
|
|
|
fsli->EffectiveAccess = ccb->access;
|
|
fsli->LxFlags = LX_FILE_METADATA_HAS_UID | LX_FILE_METADATA_HAS_GID | LX_FILE_METADATA_HAS_MODE | LX_FILE_METADATA_HAS_DEVICE_ID;
|
|
|
|
if (fcb->case_sensitive)
|
|
fsli->LxFlags |= LX_FILE_CASE_SENSITIVE_DIR;
|
|
|
|
fsli->LxUid = ii->st_uid;
|
|
fsli->LxGid = ii->st_gid;
|
|
fsli->LxMode = ii->st_mode;
|
|
|
|
if (ii->st_mode & __S_IFBLK || ii->st_mode & __S_IFCHR) {
|
|
fsli->LxDeviceIdMajor = (ii->st_rdev & 0xFFFFFFFFFFF00000) >> 20;
|
|
fsli->LxDeviceIdMinor = (ii->st_rdev & 0xFFFFF);
|
|
} else {
|
|
fsli->LxDeviceIdMajor = 0;
|
|
fsli->LxDeviceIdMinor = 0;
|
|
}
|
|
|
|
*length -= sizeof(FILE_STAT_LX_INFORMATION);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS fill_in_file_case_sensitive_information(FILE_CASE_SENSITIVE_INFORMATION* fcsi, fcb* fcb, LONG* length) {
|
|
fcsi->Flags = fcb->case_sensitive ? FILE_CS_FLAG_CASE_SENSITIVE_DIR : 0;
|
|
|
|
*length -= sizeof(FILE_CASE_SENSITIVE_INFORMATION);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
#endif
|
|
|
|
static NTSTATUS query_info(device_extension* Vcb, PFILE_OBJECT FileObject, PIRP Irp) {
|
|
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
LONG length = IrpSp->Parameters.QueryFile.Length;
|
|
fcb* fcb = FileObject->FsContext;
|
|
ccb* ccb = FileObject->FsContext2;
|
|
file_ref* fileref = ccb ? ccb->fileref : NULL;
|
|
NTSTATUS Status;
|
|
|
|
TRACE("(%p, %p, %p)\n", Vcb, FileObject, Irp);
|
|
TRACE("fcb = %p\n", fcb);
|
|
|
|
if (fcb == Vcb->volume_fcb)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
if (!ccb) {
|
|
ERR("ccb is NULL\n");
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
switch (IrpSp->Parameters.QueryFile.FileInformationClass) {
|
|
case FileAllInformation:
|
|
{
|
|
FILE_ALL_INFORMATION* fai = Irp->AssociatedIrp.SystemBuffer;
|
|
INODE_ITEM* ii;
|
|
|
|
TRACE("FileAllInformation\n");
|
|
|
|
if (Irp->RequestorMode != KernelMode && !(ccb->access & (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES))) {
|
|
WARN("insufficient privileges\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto exit;
|
|
}
|
|
|
|
if (fcb->ads) {
|
|
if (!fileref || !fileref->parent) {
|
|
ERR("no fileref for stream\n");
|
|
Status = STATUS_INTERNAL_ERROR;
|
|
goto exit;
|
|
}
|
|
|
|
ii = &fileref->parent->fcb->inode_item;
|
|
} else
|
|
ii = &fcb->inode_item;
|
|
|
|
// Access, mode, and alignment are all filled in by the kernel
|
|
|
|
if (length > 0)
|
|
fill_in_file_basic_information(&fai->BasicInformation, ii, &length, fcb, fileref);
|
|
|
|
if (length > 0)
|
|
fill_in_file_standard_information(&fai->StandardInformation, fcb, fileref, &length);
|
|
|
|
if (length > 0)
|
|
fill_in_file_internal_information(&fai->InternalInformation, fcb, &length);
|
|
|
|
if (length > 0)
|
|
fill_in_file_ea_information(&fai->EaInformation, fcb, &length);
|
|
|
|
length -= sizeof(FILE_ACCESS_INFORMATION);
|
|
|
|
if (length > 0)
|
|
fill_in_file_position_information(&fai->PositionInformation, FileObject, &length);
|
|
|
|
length -= sizeof(FILE_MODE_INFORMATION);
|
|
|
|
length -= sizeof(FILE_ALIGNMENT_INFORMATION);
|
|
|
|
if (length > 0)
|
|
fill_in_file_name_information(&fai->NameInformation, fcb, fileref, &length);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
break;
|
|
}
|
|
|
|
case FileAttributeTagInformation:
|
|
{
|
|
FILE_ATTRIBUTE_TAG_INFORMATION* ati = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileAttributeTagInformation\n");
|
|
|
|
if (Irp->RequestorMode != KernelMode && !(ccb->access & (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES))) {
|
|
WARN("insufficient privileges\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto exit;
|
|
}
|
|
|
|
ExAcquireResourceSharedLite(&Vcb->tree_lock, true);
|
|
Status = fill_in_file_attribute_information(ati, fcb, ccb, &length);
|
|
ExReleaseResourceLite(&Vcb->tree_lock);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileBasicInformation:
|
|
{
|
|
FILE_BASIC_INFORMATION* fbi = Irp->AssociatedIrp.SystemBuffer;
|
|
INODE_ITEM* ii;
|
|
|
|
TRACE("FileBasicInformation\n");
|
|
|
|
if (Irp->RequestorMode != KernelMode && !(ccb->access & (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES))) {
|
|
WARN("insufficient privileges\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto exit;
|
|
}
|
|
|
|
if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_BASIC_INFORMATION)) {
|
|
WARN("overflow\n");
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
goto exit;
|
|
}
|
|
|
|
if (fcb->ads) {
|
|
if (!fileref || !fileref->parent) {
|
|
ERR("no fileref for stream\n");
|
|
Status = STATUS_INTERNAL_ERROR;
|
|
goto exit;
|
|
}
|
|
|
|
ii = &fileref->parent->fcb->inode_item;
|
|
} else
|
|
ii = &fcb->inode_item;
|
|
|
|
Status = fill_in_file_basic_information(fbi, ii, &length, fcb, fileref);
|
|
break;
|
|
}
|
|
|
|
case FileCompressionInformation:
|
|
FIXME("STUB: FileCompressionInformation\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto exit;
|
|
|
|
case FileEaInformation:
|
|
{
|
|
FILE_EA_INFORMATION* eai = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileEaInformation\n");
|
|
|
|
Status = fill_in_file_ea_information(eai, fcb, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileInternalInformation:
|
|
{
|
|
FILE_INTERNAL_INFORMATION* fii = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileInternalInformation\n");
|
|
|
|
Status = fill_in_file_internal_information(fii, fcb, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileNameInformation:
|
|
{
|
|
FILE_NAME_INFORMATION* fni = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileNameInformation\n");
|
|
|
|
Status = fill_in_file_name_information(fni, fcb, fileref, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileNetworkOpenInformation:
|
|
{
|
|
FILE_NETWORK_OPEN_INFORMATION* fnoi = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileNetworkOpenInformation\n");
|
|
|
|
if (Irp->RequestorMode != KernelMode && !(ccb->access & (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES))) {
|
|
WARN("insufficient privileges\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto exit;
|
|
}
|
|
|
|
Status = fill_in_file_network_open_information(fnoi, fcb, fileref, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FilePositionInformation:
|
|
{
|
|
FILE_POSITION_INFORMATION* fpi = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FilePositionInformation\n");
|
|
|
|
Status = fill_in_file_position_information(fpi, FileObject, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileStandardInformation:
|
|
{
|
|
FILE_STANDARD_INFORMATION* fsi = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileStandardInformation\n");
|
|
|
|
if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_STANDARD_INFORMATION)) {
|
|
WARN("overflow\n");
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
goto exit;
|
|
}
|
|
|
|
Status = fill_in_file_standard_information(fsi, fcb, ccb->fileref, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileStreamInformation:
|
|
{
|
|
FILE_STREAM_INFORMATION* fsi = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileStreamInformation\n");
|
|
|
|
Status = fill_in_file_stream_information(fsi, fileref, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
#if (NTDDI_VERSION >= NTDDI_VISTA)
|
|
case FileHardLinkInformation:
|
|
{
|
|
FILE_LINKS_INFORMATION* fli = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileHardLinkInformation\n");
|
|
|
|
ExAcquireResourceSharedLite(&Vcb->tree_lock, true);
|
|
Status = fill_in_hard_link_information(fli, fileref, Irp, &length);
|
|
ExReleaseResourceLite(&Vcb->tree_lock);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileNormalizedNameInformation:
|
|
{
|
|
FILE_NAME_INFORMATION* fni = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileNormalizedNameInformation\n");
|
|
|
|
Status = fill_in_file_name_information(fni, fcb, fileref, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileStandardLinkInformation:
|
|
{
|
|
FILE_STANDARD_LINK_INFORMATION* fsli = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileStandardLinkInformation\n");
|
|
|
|
Status = fill_in_file_standard_link_information(fsli, fcb, ccb->fileref, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileRemoteProtocolInformation:
|
|
TRACE("FileRemoteProtocolInformation\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto exit;
|
|
|
|
#ifndef _MSC_VER
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wswitch"
|
|
#endif
|
|
case FileIdInformation:
|
|
{
|
|
FILE_ID_INFORMATION* fii = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_ID_INFORMATION)) {
|
|
WARN("overflow\n");
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
goto exit;
|
|
}
|
|
|
|
TRACE("FileIdInformation\n");
|
|
|
|
Status = fill_in_file_id_information(fii, fcb, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileStatInformation:
|
|
{
|
|
FILE_STAT_INFORMATION* fsi = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_STAT_LX_INFORMATION)) {
|
|
WARN("overflow\n");
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
goto exit;
|
|
}
|
|
|
|
TRACE("FileStatInformation\n");
|
|
|
|
Status = fill_in_file_stat_information(fsi, fcb, ccb, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileStatLxInformation:
|
|
{
|
|
FILE_STAT_LX_INFORMATION* fsli = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_STAT_LX_INFORMATION)) {
|
|
WARN("overflow\n");
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
goto exit;
|
|
}
|
|
|
|
TRACE("FileStatLxInformation\n");
|
|
|
|
Status = fill_in_file_stat_lx_information(fsli, fcb, ccb, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileCaseSensitiveInformation:
|
|
{
|
|
FILE_CASE_SENSITIVE_INFORMATION* fcsi = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_CASE_SENSITIVE_INFORMATION)) {
|
|
WARN("overflow\n");
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
goto exit;
|
|
}
|
|
|
|
TRACE("FileCaseSensitiveInformation\n");
|
|
|
|
Status = fill_in_file_case_sensitive_information(fcsi, fcb, &length);
|
|
|
|
break;
|
|
}
|
|
|
|
case FileHardLinkFullIdInformation:
|
|
{
|
|
FILE_LINKS_FULL_ID_INFORMATION* flfii = Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
TRACE("FileHardLinkFullIdInformation\n");
|
|
|
|
ExAcquireResourceSharedLite(&Vcb->tree_lock, true);
|
|
Status = fill_in_hard_link_full_id_information(flfii, fileref, Irp, &length);
|
|
ExReleaseResourceLite(&Vcb->tree_lock);
|
|
|
|
break;
|
|
}
|
|
#ifndef _MSC_VER
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
#endif
|
|
|
|
default:
|
|
WARN("unknown FileInformationClass %u\n", IrpSp->Parameters.QueryFile.FileInformationClass);
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto exit;
|
|
}
|
|
|
|
if (length < 0) {
|
|
length = 0;
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
Irp->IoStatus.Information = IrpSp->Parameters.QueryFile.Length - length;
|
|
|
|
exit:
|
|
TRACE("query_info returning %08x\n", Status);
|
|
|
|
return Status;
|
|
}
|
|
|
|
_Dispatch_type_(IRP_MJ_QUERY_INFORMATION)
|
|
_Function_class_(DRIVER_DISPATCH)
|
|
NTSTATUS __stdcall drv_query_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
|
|
PIO_STACK_LOCATION IrpSp;
|
|
NTSTATUS Status;
|
|
fcb* fcb;
|
|
device_extension* Vcb = DeviceObject->DeviceExtension;
|
|
bool top_level;
|
|
|
|
FsRtlEnterFileSystem();
|
|
|
|
top_level = is_top_level(Irp);
|
|
|
|
if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {
|
|
Status = vol_query_information(DeviceObject, Irp);
|
|
goto end;
|
|
} else if (!Vcb || Vcb->type != VCB_TYPE_FS) {
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
TRACE("query information\n");
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
fcb = IrpSp->FileObject->FsContext;
|
|
TRACE("fcb = %p\n", fcb);
|
|
TRACE("fcb->subvol = %p\n", fcb->subvol);
|
|
|
|
Status = query_info(fcb->Vcb, IrpSp->FileObject, Irp);
|
|
|
|
end:
|
|
TRACE("returning %08x\n", Status);
|
|
|
|
Irp->IoStatus.Status = Status;
|
|
|
|
IoCompleteRequest( Irp, IO_NO_INCREMENT );
|
|
|
|
if (top_level)
|
|
IoSetTopLevelIrp(NULL);
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
return Status;
|
|
}
|
|
|
|
_Dispatch_type_(IRP_MJ_QUERY_EA)
|
|
_Function_class_(DRIVER_DISPATCH)
|
|
NTSTATUS __stdcall drv_query_ea(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
|
|
NTSTATUS Status;
|
|
bool top_level;
|
|
device_extension* Vcb = DeviceObject->DeviceExtension;
|
|
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
PFILE_OBJECT FileObject = IrpSp->FileObject;
|
|
fcb* fcb;
|
|
ccb* ccb;
|
|
FILE_FULL_EA_INFORMATION* ffei;
|
|
ULONG retlen = 0;
|
|
|
|
FsRtlEnterFileSystem();
|
|
|
|
TRACE("(%p, %p)\n", DeviceObject, Irp);
|
|
|
|
top_level = is_top_level(Irp);
|
|
|
|
if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {
|
|
Status = vol_query_ea(DeviceObject, Irp);
|
|
goto end;
|
|
} else if (!Vcb || Vcb->type != VCB_TYPE_FS) {
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
ffei = map_user_buffer(Irp, NormalPagePriority);
|
|
if (!ffei) {
|
|
ERR("could not get output buffer\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
if (!FileObject) {
|
|
ERR("no file object\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
fcb = FileObject->FsContext;
|
|
|
|
if (!fcb) {
|
|
ERR("no fcb\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
ccb = FileObject->FsContext2;
|
|
|
|
if (!ccb) {
|
|
ERR("no ccb\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
if (Irp->RequestorMode == UserMode && !(ccb->access & (FILE_READ_EA | FILE_WRITE_EA))) {
|
|
WARN("insufficient privileges\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
|
|
if (fcb->ads)
|
|
fcb = ccb->fileref->parent->fcb;
|
|
|
|
ExAcquireResourceSharedLite(fcb->Header.Resource, true);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
if (fcb->ea_xattr.Length == 0)
|
|
goto end2;
|
|
|
|
if (IrpSp->Parameters.QueryEa.EaList) {
|
|
FILE_FULL_EA_INFORMATION *ea, *out;
|
|
FILE_GET_EA_INFORMATION* in;
|
|
|
|
in = IrpSp->Parameters.QueryEa.EaList;
|
|
do {
|
|
STRING s;
|
|
|
|
s.Length = s.MaximumLength = in->EaNameLength;
|
|
s.Buffer = in->EaName;
|
|
|
|
RtlUpperString(&s, &s);
|
|
|
|
if (in->NextEntryOffset == 0)
|
|
break;
|
|
|
|
in = (FILE_GET_EA_INFORMATION*)(((uint8_t*)in) + in->NextEntryOffset);
|
|
} while (true);
|
|
|
|
ea = (FILE_FULL_EA_INFORMATION*)fcb->ea_xattr.Buffer;
|
|
out = NULL;
|
|
|
|
do {
|
|
bool found = false;
|
|
|
|
in = IrpSp->Parameters.QueryEa.EaList;
|
|
do {
|
|
if (in->EaNameLength == ea->EaNameLength &&
|
|
RtlCompareMemory(in->EaName, ea->EaName, in->EaNameLength) == in->EaNameLength) {
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
if (in->NextEntryOffset == 0)
|
|
break;
|
|
|
|
in = (FILE_GET_EA_INFORMATION*)(((uint8_t*)in) + in->NextEntryOffset);
|
|
} while (true);
|
|
|
|
if (found) {
|
|
uint8_t padding = retlen % 4 > 0 ? (4 - (retlen % 4)) : 0;
|
|
|
|
if (offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + ea->EaNameLength + 1 + ea->EaValueLength > IrpSp->Parameters.QueryEa.Length - retlen - padding) {
|
|
Status = STATUS_BUFFER_OVERFLOW;
|
|
retlen = 0;
|
|
goto end2;
|
|
}
|
|
|
|
retlen += padding;
|
|
|
|
if (out) {
|
|
out->NextEntryOffset = (ULONG)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + out->EaNameLength + 1 + out->EaValueLength + padding;
|
|
out = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)out) + out->NextEntryOffset);
|
|
} else
|
|
out = ffei;
|
|
|
|
out->NextEntryOffset = 0;
|
|
out->Flags = ea->Flags;
|
|
out->EaNameLength = ea->EaNameLength;
|
|
out->EaValueLength = ea->EaValueLength;
|
|
RtlCopyMemory(out->EaName, ea->EaName, ea->EaNameLength + ea->EaValueLength + 1);
|
|
|
|
retlen += (ULONG)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + ea->EaNameLength + 1 + ea->EaValueLength;
|
|
|
|
if (IrpSp->Flags & SL_RETURN_SINGLE_ENTRY)
|
|
break;
|
|
}
|
|
|
|
if (ea->NextEntryOffset == 0)
|
|
break;
|
|
|
|
ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset);
|
|
} while (true);
|
|
} else {
|
|
FILE_FULL_EA_INFORMATION *ea, *out;
|
|
ULONG index;
|
|
|
|
if (IrpSp->Flags & SL_INDEX_SPECIFIED) {
|
|
// The index is 1-based
|
|
if (IrpSp->Parameters.QueryEa.EaIndex == 0) {
|
|
Status = STATUS_NONEXISTENT_EA_ENTRY;
|
|
goto end2;
|
|
} else
|
|
index = IrpSp->Parameters.QueryEa.EaIndex - 1;
|
|
} else if (IrpSp->Flags & SL_RESTART_SCAN)
|
|
index = ccb->ea_index = 0;
|
|
else
|
|
index = ccb->ea_index;
|
|
|
|
ea = (FILE_FULL_EA_INFORMATION*)fcb->ea_xattr.Buffer;
|
|
|
|
if (index > 0) {
|
|
ULONG i;
|
|
|
|
for (i = 0; i < index; i++) {
|
|
if (ea->NextEntryOffset == 0) // last item
|
|
goto end2;
|
|
|
|
ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset);
|
|
}
|
|
}
|
|
|
|
out = NULL;
|
|
|
|
do {
|
|
uint8_t padding = retlen % 4 > 0 ? (4 - (retlen % 4)) : 0;
|
|
|
|
if (offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + ea->EaNameLength + 1 + ea->EaValueLength > IrpSp->Parameters.QueryEa.Length - retlen - padding) {
|
|
Status = retlen == 0 ? STATUS_BUFFER_TOO_SMALL : STATUS_BUFFER_OVERFLOW;
|
|
goto end2;
|
|
}
|
|
|
|
retlen += padding;
|
|
|
|
if (out) {
|
|
out->NextEntryOffset = (ULONG)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + out->EaNameLength + 1 + out->EaValueLength + padding;
|
|
out = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)out) + out->NextEntryOffset);
|
|
} else
|
|
out = ffei;
|
|
|
|
out->NextEntryOffset = 0;
|
|
out->Flags = ea->Flags;
|
|
out->EaNameLength = ea->EaNameLength;
|
|
out->EaValueLength = ea->EaValueLength;
|
|
RtlCopyMemory(out->EaName, ea->EaName, ea->EaNameLength + ea->EaValueLength + 1);
|
|
|
|
retlen += (ULONG)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + ea->EaNameLength + 1 + ea->EaValueLength;
|
|
|
|
if (!(IrpSp->Flags & SL_INDEX_SPECIFIED))
|
|
ccb->ea_index++;
|
|
|
|
if (ea->NextEntryOffset == 0 || IrpSp->Flags & SL_RETURN_SINGLE_ENTRY)
|
|
break;
|
|
|
|
ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset);
|
|
} while (true);
|
|
}
|
|
|
|
end2:
|
|
ExReleaseResourceLite(fcb->Header.Resource);
|
|
|
|
end:
|
|
TRACE("returning %08x\n", Status);
|
|
|
|
Irp->IoStatus.Status = Status;
|
|
Irp->IoStatus.Information = NT_SUCCESS(Status) || Status == STATUS_BUFFER_OVERFLOW ? retlen : 0;
|
|
|
|
IoCompleteRequest( Irp, IO_NO_INCREMENT );
|
|
|
|
if (top_level)
|
|
IoSetTopLevelIrp(NULL);
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
return Status;
|
|
}
|
|
|
|
_Dispatch_type_(IRP_MJ_SET_EA)
|
|
_Function_class_(DRIVER_DISPATCH)
|
|
NTSTATUS __stdcall drv_set_ea(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {
|
|
device_extension* Vcb = DeviceObject->DeviceExtension;
|
|
NTSTATUS Status;
|
|
bool top_level;
|
|
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
PFILE_OBJECT FileObject = IrpSp->FileObject;
|
|
fcb* fcb;
|
|
ccb* ccb;
|
|
file_ref* fileref;
|
|
FILE_FULL_EA_INFORMATION* ffei;
|
|
ULONG offset;
|
|
LIST_ENTRY ealist;
|
|
ea_item* item;
|
|
FILE_FULL_EA_INFORMATION* ea;
|
|
LIST_ENTRY* le;
|
|
LARGE_INTEGER time;
|
|
BTRFS_TIME now;
|
|
|
|
FsRtlEnterFileSystem();
|
|
|
|
TRACE("(%p, %p)\n", DeviceObject, Irp);
|
|
|
|
top_level = is_top_level(Irp);
|
|
|
|
if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {
|
|
Status = vol_set_ea(DeviceObject, Irp);
|
|
goto end;
|
|
} else if (!Vcb || Vcb->type != VCB_TYPE_FS) {
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
if (Vcb->readonly) {
|
|
Status = STATUS_MEDIA_WRITE_PROTECTED;
|
|
goto end;
|
|
}
|
|
|
|
ffei = map_user_buffer(Irp, NormalPagePriority);
|
|
if (!ffei) {
|
|
ERR("could not get output buffer\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
Status = IoCheckEaBufferValidity(ffei, IrpSp->Parameters.SetEa.Length, &offset);
|
|
if (!NT_SUCCESS(Status)) {
|
|
ERR("IoCheckEaBufferValidity returned %08x (error at offset %u)\n", Status, offset);
|
|
goto end;
|
|
}
|
|
|
|
if (!FileObject) {
|
|
ERR("no file object\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
fcb = FileObject->FsContext;
|
|
|
|
if (!fcb) {
|
|
ERR("no fcb\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
ccb = FileObject->FsContext2;
|
|
|
|
if (!ccb) {
|
|
ERR("no ccb\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end;
|
|
}
|
|
|
|
if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_EA)) {
|
|
WARN("insufficient privileges\n");
|
|
Status = STATUS_ACCESS_DENIED;
|
|
goto end;
|
|
}
|
|
|
|
if (fcb->ads) {
|
|
fileref = ccb->fileref->parent;
|
|
fcb = fileref->fcb;
|
|
} else
|
|
fileref = ccb->fileref;
|
|
|
|
InitializeListHead(&ealist);
|
|
|
|
ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);
|
|
|
|
if (fcb->ea_xattr.Length > 0) {
|
|
ea = (FILE_FULL_EA_INFORMATION*)fcb->ea_xattr.Buffer;
|
|
|
|
do {
|
|
item = ExAllocatePoolWithTag(PagedPool, sizeof(ea_item), ALLOC_TAG);
|
|
if (!item) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end2;
|
|
}
|
|
|
|
item->name.Length = item->name.MaximumLength = ea->EaNameLength;
|
|
item->name.Buffer = ea->EaName;
|
|
|
|
item->value.Length = item->value.MaximumLength = ea->EaValueLength;
|
|
item->value.Buffer = &ea->EaName[ea->EaNameLength + 1];
|
|
|
|
item->flags = ea->Flags;
|
|
|
|
InsertTailList(&ealist, &item->list_entry);
|
|
|
|
if (ea->NextEntryOffset == 0)
|
|
break;
|
|
|
|
ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset);
|
|
} while (true);
|
|
}
|
|
|
|
ea = ffei;
|
|
|
|
do {
|
|
STRING s;
|
|
bool found = false;
|
|
|
|
s.Length = s.MaximumLength = ea->EaNameLength;
|
|
s.Buffer = ea->EaName;
|
|
|
|
RtlUpperString(&s, &s);
|
|
|
|
le = ealist.Flink;
|
|
while (le != &ealist) {
|
|
item = CONTAINING_RECORD(le, ea_item, list_entry);
|
|
|
|
if (item->name.Length == s.Length &&
|
|
RtlCompareMemory(item->name.Buffer, s.Buffer, s.Length) == s.Length) {
|
|
item->flags = ea->Flags;
|
|
item->value.Length = item->value.MaximumLength = ea->EaValueLength;
|
|
item->value.Buffer = &ea->EaName[ea->EaNameLength + 1];
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
if (!found) {
|
|
item = ExAllocatePoolWithTag(PagedPool, sizeof(ea_item), ALLOC_TAG);
|
|
if (!item) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end2;
|
|
}
|
|
|
|
item->name.Length = item->name.MaximumLength = ea->EaNameLength;
|
|
item->name.Buffer = ea->EaName;
|
|
|
|
item->value.Length = item->value.MaximumLength = ea->EaValueLength;
|
|
item->value.Buffer = &ea->EaName[ea->EaNameLength + 1];
|
|
|
|
item->flags = ea->Flags;
|
|
|
|
InsertTailList(&ealist, &item->list_entry);
|
|
}
|
|
|
|
if (ea->NextEntryOffset == 0)
|
|
break;
|
|
|
|
ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset);
|
|
} while (true);
|
|
|
|
// remove entries with zero-length value
|
|
le = ealist.Flink;
|
|
while (le != &ealist) {
|
|
LIST_ENTRY* le2 = le->Flink;
|
|
|
|
item = CONTAINING_RECORD(le, ea_item, list_entry);
|
|
|
|
if (item->value.Length == 0) {
|
|
RemoveEntryList(&item->list_entry);
|
|
ExFreePool(item);
|
|
}
|
|
|
|
le = le2;
|
|
}
|
|
|
|
// handle LXSS values
|
|
le = ealist.Flink;
|
|
while (le != &ealist) {
|
|
LIST_ENTRY* le2 = le->Flink;
|
|
|
|
item = CONTAINING_RECORD(le, ea_item, list_entry);
|
|
|
|
if (item->name.Length == sizeof(lxuid) - 1 && RtlCompareMemory(item->name.Buffer, lxuid, item->name.Length) == item->name.Length) {
|
|
if (item->value.Length < sizeof(uint32_t)) {
|
|
ERR("uid value was shorter than expected\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end2;
|
|
}
|
|
|
|
if (Irp->RequestorMode == KernelMode) {
|
|
RtlCopyMemory(&fcb->inode_item.st_uid, item->value.Buffer, sizeof(uint32_t));
|
|
fcb->sd_dirty = true;
|
|
fcb->sd_deleted = false;
|
|
}
|
|
|
|
RemoveEntryList(&item->list_entry);
|
|
ExFreePool(item);
|
|
} else if (item->name.Length == sizeof(lxgid) - 1 && RtlCompareMemory(item->name.Buffer, lxgid, item->name.Length) == item->name.Length) {
|
|
if (item->value.Length < sizeof(uint32_t)) {
|
|
ERR("gid value was shorter than expected\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end2;
|
|
}
|
|
|
|
if (Irp->RequestorMode == KernelMode)
|
|
RtlCopyMemory(&fcb->inode_item.st_gid, item->value.Buffer, sizeof(uint32_t));
|
|
|
|
RemoveEntryList(&item->list_entry);
|
|
ExFreePool(item);
|
|
} else if (item->name.Length == sizeof(lxmod) - 1 && RtlCompareMemory(item->name.Buffer, lxmod, item->name.Length) == item->name.Length) {
|
|
if (item->value.Length < sizeof(uint32_t)) {
|
|
ERR("mode value was shorter than expected\n");
|
|
Status = STATUS_INVALID_PARAMETER;
|
|
goto end2;
|
|
}
|
|
|
|
if (Irp->RequestorMode == KernelMode) {
|
|
uint32_t allowed = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH | S_ISGID | S_ISVTX | S_ISUID;
|
|
uint32_t val;
|
|
|
|
RtlCopyMemory(&val, item->value.Buffer, sizeof(uint32_t));
|
|
|
|
fcb->inode_item.st_mode &= ~allowed;
|
|
fcb->inode_item.st_mode |= val & allowed;
|
|
}
|
|
|
|
RemoveEntryList(&item->list_entry);
|
|
ExFreePool(item);
|
|
}
|
|
|
|
le = le2;
|
|
}
|
|
|
|
if (IsListEmpty(&ealist)) {
|
|
fcb->ealen = 0;
|
|
|
|
if (fcb->ea_xattr.Buffer)
|
|
ExFreePool(fcb->ea_xattr.Buffer);
|
|
|
|
fcb->ea_xattr.Length = fcb->ea_xattr.MaximumLength = 0;
|
|
fcb->ea_xattr.Buffer = NULL;
|
|
} else {
|
|
uint16_t size = 0;
|
|
char *buf, *oldbuf;
|
|
|
|
le = ealist.Flink;
|
|
while (le != &ealist) {
|
|
item = CONTAINING_RECORD(le, ea_item, list_entry);
|
|
|
|
if (size % 4 > 0)
|
|
size += 4 - (size % 4);
|
|
|
|
size += (uint16_t)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + item->name.Length + 1 + item->value.Length;
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
buf = ExAllocatePoolWithTag(PagedPool, size, ALLOC_TAG);
|
|
if (!buf) {
|
|
ERR("out of memory\n");
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto end2;
|
|
}
|
|
|
|
oldbuf = fcb->ea_xattr.Buffer;
|
|
|
|
fcb->ea_xattr.Length = fcb->ea_xattr.MaximumLength = size;
|
|
fcb->ea_xattr.Buffer = buf;
|
|
|
|
fcb->ealen = 4;
|
|
ea = NULL;
|
|
|
|
le = ealist.Flink;
|
|
while (le != &ealist) {
|
|
item = CONTAINING_RECORD(le, ea_item, list_entry);
|
|
|
|
if (ea) {
|
|
ea->NextEntryOffset = (ULONG)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + ea->EaNameLength + ea->EaValueLength;
|
|
|
|
if (ea->NextEntryOffset % 4 > 0)
|
|
ea->NextEntryOffset += 4 - (ea->NextEntryOffset % 4);
|
|
|
|
ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset);
|
|
} else
|
|
ea = (FILE_FULL_EA_INFORMATION*)fcb->ea_xattr.Buffer;
|
|
|
|
ea->NextEntryOffset = 0;
|
|
ea->Flags = item->flags;
|
|
ea->EaNameLength = (UCHAR)item->name.Length;
|
|
ea->EaValueLength = item->value.Length;
|
|
|
|
RtlCopyMemory(ea->EaName, item->name.Buffer, item->name.Length);
|
|
ea->EaName[item->name.Length] = 0;
|
|
RtlCopyMemory(&ea->EaName[item->name.Length + 1], item->value.Buffer, item->value.Length);
|
|
|
|
fcb->ealen += 5 + item->name.Length + item->value.Length;
|
|
|
|
le = le->Flink;
|
|
}
|
|
|
|
if (oldbuf)
|
|
ExFreePool(oldbuf);
|
|
}
|
|
|
|
fcb->ea_changed = true;
|
|
|
|
KeQuerySystemTime(&time);
|
|
win_time_to_unix(time, &now);
|
|
|
|
fcb->inode_item.transid = Vcb->superblock.generation;
|
|
fcb->inode_item.sequence++;
|
|
|
|
if (!ccb->user_set_change_time)
|
|
fcb->inode_item.st_ctime = now;
|
|
|
|
fcb->inode_item_changed = true;
|
|
mark_fcb_dirty(fcb);
|
|
|
|
send_notification_fileref(fileref, FILE_NOTIFY_CHANGE_EA, FILE_ACTION_MODIFIED, NULL);
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
end2:
|
|
ExReleaseResourceLite(fcb->Header.Resource);
|
|
|
|
while (!IsListEmpty(&ealist)) {
|
|
le = RemoveHeadList(&ealist);
|
|
|
|
item = CONTAINING_RECORD(le, ea_item, list_entry);
|
|
|
|
ExFreePool(item);
|
|
}
|
|
|
|
end:
|
|
TRACE("returning %08x\n", Status);
|
|
|
|
Irp->IoStatus.Status = Status;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
|
|
if (top_level)
|
|
IoSetTopLevelIrp(NULL);
|
|
|
|
FsRtlExitFileSystem();
|
|
|
|
return Status;
|
|
}
|