1193 lines
33 KiB
C
1193 lines
33 KiB
C
/*++
|
|
|
|
Copyright (c) 2000-2001 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
fileinfo.c
|
|
|
|
Abstract:
|
|
|
|
This module implements routines related to handling
|
|
IRP_MJ_QUERY_INFORMATION and IRP_MJ_SET_INFORMATION.
|
|
|
|
--*/
|
|
|
|
#include "fatx.h"
|
|
|
|
VOID
|
|
FatxQueryNetworkOpenInformation(
|
|
IN PFILE_OBJECT FileObject,
|
|
OUT PFILE_NETWORK_OPEN_INFORMATION NetworkOpenInformation
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine fills the information structure with attributes about the
|
|
supplied file object.
|
|
|
|
Arguments:
|
|
|
|
FileObject - Specifies the file object to obtain the information from.
|
|
|
|
NetworkOpenInformation - Specifies the buffer to receive the file
|
|
information.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
{
|
|
PFAT_FCB Fcb;
|
|
|
|
Fcb = (PFAT_FCB)FileObject->FsContext;
|
|
|
|
ASSERT(FatxIsFlagClear(Fcb->Flags, FAT_FCB_VOLUME));
|
|
|
|
NetworkOpenInformation->CreationTime =
|
|
FatxFatTimestampToTime(&Fcb->CreationTime);
|
|
NetworkOpenInformation->LastAccessTime =
|
|
FatxFatTimestampToTime(&Fcb->LastAccessTime);
|
|
NetworkOpenInformation->LastWriteTime = FatxRoundToFatTime(&Fcb->LastWriteTime);
|
|
NetworkOpenInformation->ChangeTime = NetworkOpenInformation->LastWriteTime;
|
|
|
|
if (Fcb->FileAttributes == 0) {
|
|
NetworkOpenInformation->FileAttributes = FILE_ATTRIBUTE_NORMAL;
|
|
} else {
|
|
NetworkOpenInformation->FileAttributes = Fcb->FileAttributes;
|
|
}
|
|
|
|
if (FatxIsFlagSet(Fcb->Flags, FAT_FCB_DIRECTORY)) {
|
|
|
|
NetworkOpenInformation->AllocationSize.QuadPart = 0;
|
|
NetworkOpenInformation->EndOfFile.QuadPart = 0;
|
|
|
|
} else {
|
|
|
|
//
|
|
// AllocationSize should be filled in with the number of clusters
|
|
// actually allocated to the file, but we don't want to have to go parse
|
|
// the entire FAT chain to obtain this piece of information. We'll let
|
|
// the caller assume that the file has been allocated the number of
|
|
// bytes actually in the file.
|
|
//
|
|
|
|
NetworkOpenInformation->AllocationSize.QuadPart = (ULONGLONG)Fcb->FileSize;
|
|
NetworkOpenInformation->EndOfFile.QuadPart = (ULONGLONG)Fcb->FileSize;
|
|
}
|
|
}
|
|
|
|
NTSTATUS
|
|
FatxFsdQueryInformation(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by the I/O manager to handle IRP_MJ_QUERY_INFORMATION
|
|
requests.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Specifies the device object that the I/O request is for.
|
|
|
|
Irp - Specifies the packet that describes the I/O request.
|
|
|
|
Return Value:
|
|
|
|
Status of operation.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
PFAT_VOLUME_EXTENSION VolumeExtension;
|
|
PIO_STACK_LOCATION IrpSp;
|
|
PFILE_OBJECT FileObject;
|
|
PFAT_FCB Fcb;
|
|
ULONG BytesWritten;
|
|
PFILE_INTERNAL_INFORMATION InternalInformation;
|
|
PFILE_POSITION_INFORMATION PositionInformation;
|
|
|
|
VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension;
|
|
IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
FileObject = IrpSp->FileObject;
|
|
Fcb = (PFAT_FCB)FileObject->FsContext;
|
|
|
|
FatxAcquireVolumeMutexShared(VolumeExtension);
|
|
|
|
//
|
|
// Check if the volume has been dismounted.
|
|
//
|
|
|
|
if (FatxIsFlagSet(VolumeExtension->Flags, FAT_VOLUME_DISMOUNTED)) {
|
|
status = STATUS_VOLUME_DISMOUNTED;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// Check if the file object has already been cleaned up. We don't allow a
|
|
// a file object to be accessed after its handle has been closed.
|
|
//
|
|
|
|
if (FatxIsFlagSet(FileObject->Flags, FO_CLEANUP_COMPLETE)) {
|
|
status = STATUS_FILE_CLOSED;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// For volume file control blocks, the only thing that can be queried is the
|
|
// current file position.
|
|
//
|
|
|
|
if (FatxIsFlagSet(Fcb->Flags, FAT_FCB_VOLUME) &&
|
|
(IrpSp->Parameters.SetFile.FileInformationClass != FilePositionInformation)) {
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// Clear the output buffer.
|
|
//
|
|
|
|
RtlZeroMemory(Irp->UserBuffer, IrpSp->Parameters.QueryFile.Length);
|
|
|
|
//
|
|
// Dispatch the information class function.
|
|
//
|
|
|
|
status = STATUS_SUCCESS;
|
|
|
|
switch (IrpSp->Parameters.QueryFile.FileInformationClass) {
|
|
|
|
case FileInternalInformation:
|
|
InternalInformation = (PFILE_INTERNAL_INFORMATION)Irp->UserBuffer;
|
|
InternalInformation->IndexNumber.HighPart = PtrToUlong(VolumeExtension);
|
|
InternalInformation->IndexNumber.LowPart = PtrToUlong(Fcb);
|
|
BytesWritten = sizeof(FILE_INTERNAL_INFORMATION);
|
|
break;
|
|
|
|
case FilePositionInformation:
|
|
PositionInformation = (PFILE_POSITION_INFORMATION)Irp->UserBuffer;
|
|
PositionInformation->CurrentByteOffset = FileObject->CurrentByteOffset;
|
|
BytesWritten = sizeof(FILE_POSITION_INFORMATION);
|
|
break;
|
|
|
|
case FileNetworkOpenInformation:
|
|
FatxQueryNetworkOpenInformation(FileObject,
|
|
(PFILE_NETWORK_OPEN_INFORMATION)Irp->UserBuffer);
|
|
BytesWritten = sizeof(FILE_NETWORK_OPEN_INFORMATION);
|
|
break;
|
|
|
|
default:
|
|
BytesWritten = 0;
|
|
status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Fill in the number of bytes that we wrote to the user's buffer.
|
|
//
|
|
|
|
Irp->IoStatus.Information = BytesWritten;
|
|
|
|
//
|
|
// Check that we didn't overflow the user's buffer. The I/O manager does
|
|
// the initial check to make sure there's enough space for the static
|
|
// structure for a given information class, but we might overflow the buffer
|
|
// when copying in the variable length file name.
|
|
//
|
|
|
|
ASSERT(Irp->IoStatus.Information <= IrpSp->Parameters.QueryFile.Length);
|
|
|
|
CleanupAndExit:
|
|
FatxReleaseVolumeMutex(VolumeExtension);
|
|
|
|
Irp->IoStatus.Status = status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
FatxSetBasicInformation(
|
|
IN PFAT_VOLUME_EXTENSION VolumeExtension,
|
|
IN PIRP Irp,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PFILE_BASIC_INFORMATION BasicInformation
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine changes the basic information of the supplied file object.
|
|
|
|
Arguments:
|
|
|
|
VolumeExtension - Specifies the extension that the I/O request is for.
|
|
|
|
Irp - Specifies the packet that describes the I/O request.
|
|
|
|
FileObject - Specifies the file object to apply the information to.
|
|
|
|
BasicInformation - Specifies the basic information to apply to the file.
|
|
|
|
Return Value:
|
|
|
|
Status of operation.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
BOOLEAN ChangesMade;
|
|
PFAT_FCB Fcb;
|
|
FAT_TIME_STAMP CreationTime;
|
|
LARGE_INTEGER LastWriteTime;
|
|
FAT_TIME_STAMP FatTimestamp;
|
|
FAT_TIME_STAMP LastAccessTime;
|
|
UCHAR FileAttributes;
|
|
|
|
ChangesMade = FALSE;
|
|
Fcb = (PFAT_FCB)FileObject->FsContext;
|
|
|
|
ASSERT(FatxIsFlagClear(Fcb->Flags, FAT_FCB_VOLUME));
|
|
ASSERT(VolumeExtension->VolumeMutexExclusiveOwner == KeGetCurrentThread());
|
|
|
|
//
|
|
// Validate all of the timestamps from the information structure. If the
|
|
// timestamp is -1, then that means that the user doesn't want the file
|
|
// system automatically updating the timestamps, such as modifying the last
|
|
// write time when writing to the file. If the timestamp is zero, then no
|
|
// change should be made to that timestamp.
|
|
//
|
|
|
|
if ((BasicInformation->CreationTime.QuadPart == -1) ||
|
|
(BasicInformation->CreationTime.QuadPart == 0)) {
|
|
|
|
//
|
|
// Don't change the creation time. Grab the current creation time from
|
|
// the file control block.
|
|
//
|
|
|
|
CreationTime = Fcb->CreationTime;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Convert the NT system time to a FAT time.
|
|
//
|
|
|
|
if (!FatxTimeToFatTimestamp(&BasicInformation->CreationTime,
|
|
&CreationTime)) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (CreationTime.AsULONG != Fcb->CreationTime.AsULONG) {
|
|
ChangesMade = TRUE;
|
|
}
|
|
}
|
|
|
|
if ((BasicInformation->LastWriteTime.QuadPart == -1) ||
|
|
(BasicInformation->LastWriteTime.QuadPart == 0)) {
|
|
|
|
//
|
|
// Don't change the last write time. Grab the current last write time
|
|
// from the file control block.
|
|
//
|
|
|
|
LastWriteTime = Fcb->LastWriteTime;
|
|
|
|
//
|
|
// If the caller doesn't want us updating the last write time, then
|
|
// mark the file control block.
|
|
//
|
|
|
|
if (BasicInformation->LastWriteTime.LowPart == -1) {
|
|
Fcb->Flags |= FAT_FCB_DISABLE_LAST_WRITE_TIME;
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Verify that we can convert the time to a FAT timestamp. At this
|
|
// point, we don't care about the result.
|
|
//
|
|
|
|
if (!FatxTimeToFatTimestamp(&BasicInformation->LastWriteTime,
|
|
&FatTimestamp)) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
LastWriteTime = BasicInformation->LastWriteTime;
|
|
|
|
if (LastWriteTime.QuadPart != Fcb->LastWriteTime.QuadPart) {
|
|
ChangesMade = TRUE;
|
|
}
|
|
}
|
|
|
|
if ((BasicInformation->LastAccessTime.QuadPart == -1) ||
|
|
(BasicInformation->LastAccessTime.QuadPart == 0)) {
|
|
|
|
//
|
|
// Don't change the last access time. Grab the current last access time
|
|
// from the file control block.
|
|
//
|
|
|
|
LastAccessTime = Fcb->LastAccessTime;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Convert the NT system time to a FAT time.
|
|
//
|
|
|
|
if (!FatxTimeToFatTimestamp(&BasicInformation->LastAccessTime,
|
|
&LastAccessTime)) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (LastAccessTime.AsULONG != Fcb->LastAccessTime.AsULONG) {
|
|
ChangesMade = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Validate the file attributes.
|
|
//
|
|
|
|
if (BasicInformation->FileAttributes == 0) {
|
|
|
|
//
|
|
// Don't change the file attributes. Grab the current file attributes
|
|
// from the file control block.
|
|
//
|
|
|
|
FileAttributes = Fcb->FileAttributes;
|
|
|
|
} else {
|
|
|
|
FileAttributes =
|
|
(UCHAR)FatxFilterFileAttributes(BasicInformation->FileAttributes);
|
|
|
|
//
|
|
// Make sure that the directory attribute doesn't change.
|
|
//
|
|
|
|
if (FatxIsFlagSet(Fcb->Flags, FAT_FCB_DIRECTORY)) {
|
|
FileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
|
|
} else if (FatxIsFlagSet(FileAttributes, FILE_ATTRIBUTE_DIRECTORY)) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
if (FileAttributes != Fcb->FileAttributes) {
|
|
ChangesMade = TRUE;
|
|
}
|
|
}
|
|
|
|
if (ChangesMade) {
|
|
|
|
//
|
|
// Now that we've validated all of the input from the information
|
|
// structure, apply the changes to the file control block and write out
|
|
// the changes. If the write fails, we can live with the in-memory copy
|
|
// of these attributes being different than the on-disk copies.
|
|
//
|
|
|
|
Fcb->CreationTime = CreationTime;
|
|
Fcb->LastWriteTime = LastWriteTime;
|
|
Fcb->LastAccessTime = LastAccessTime;
|
|
Fcb->FileAttributes = FileAttributes;
|
|
|
|
status = FatxUpdateDirectoryEntry(VolumeExtension, Irp, Fcb);
|
|
|
|
} else {
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
FatxSetRenameInformation(
|
|
IN PFAT_VOLUME_EXTENSION VolumeExtension,
|
|
IN PIRP Irp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine renames the supplied file object.
|
|
|
|
Arguments:
|
|
|
|
VolumeExtension - Specifies the extension that the I/O request is for.
|
|
|
|
Irp - Specifies the packet that describes the I/O request.
|
|
|
|
Return Value:
|
|
|
|
Status of operation.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
PIO_STACK_LOCATION IrpSp;
|
|
PFILE_OBJECT FileObject;
|
|
PFAT_FCB Fcb;
|
|
PFILE_RENAME_INFORMATION RenameInformation;
|
|
POSTR OriginalFileNameBuffer;
|
|
UCHAR OriginalFileNameLength;
|
|
POSTR EndOfFileName;
|
|
POSTR StartOfFileName;
|
|
OBJECT_STRING TargetFileName;
|
|
POSTR TargetFileNameBuffer;
|
|
PFAT_FCB TargetDirectoryFcb;
|
|
ULONG NewPathNameLength;
|
|
DIRENT DirectoryEntry;
|
|
ULONG DirectoryByteOffset;
|
|
ULONG EmptyDirectoryByteOffset;
|
|
PFAT_FCB FoundOrNewFcb;
|
|
|
|
IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
FileObject = IrpSp->FileObject;
|
|
Fcb = (PFAT_FCB)FileObject->FsContext;
|
|
RenameInformation = (PFILE_RENAME_INFORMATION)Irp->UserBuffer;
|
|
|
|
ASSERT(FatxIsFlagClear(Fcb->Flags, FAT_FCB_VOLUME));
|
|
ASSERT(VolumeExtension->VolumeMutexExclusiveOwner == KeGetCurrentThread());
|
|
|
|
//
|
|
// It's possible for the file control block to be NULL if a file failed to
|
|
// be moved from one directory to another. In that case,
|
|
// FatxSetRenameInformation sets the file's parent file control block to
|
|
// NULL.
|
|
//
|
|
|
|
if (Fcb->ParentFcb == NULL) {
|
|
return STATUS_FILE_CORRUPT_ERROR;
|
|
}
|
|
|
|
//
|
|
// There must be at least some characters in the file name.
|
|
//
|
|
|
|
if (RenameInformation->FileName.Length == 0) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Save off the original file name buffer and length so that we can attempt
|
|
// to unroll errors below.
|
|
//
|
|
|
|
OriginalFileNameBuffer = Fcb->FileNameBuffer;
|
|
OriginalFileNameLength = Fcb->FileNameLength;
|
|
|
|
ASSERT(OriginalFileNameBuffer != NULL);
|
|
|
|
//
|
|
// Figure out the starting and ending (exclusive) range of the target file
|
|
// name. In a normal NT file system, the target file name would have been
|
|
// attached to the target file object, but we don't keep the file name as
|
|
// part of the file objects, so we have to go back to user's original
|
|
// buffer.
|
|
//
|
|
// For compatibility with NT, if the name ends with a backslash, ignore the
|
|
// character.
|
|
//
|
|
|
|
EndOfFileName = (POSTR)((PCHAR)RenameInformation->FileName.Buffer +
|
|
RenameInformation->FileName.Length);
|
|
|
|
if (*(EndOfFileName - 1) == OBJ_NAME_PATH_SEPARATOR) {
|
|
EndOfFileName--;
|
|
}
|
|
|
|
StartOfFileName = EndOfFileName;
|
|
|
|
while (StartOfFileName > RenameInformation->FileName.Buffer) {
|
|
|
|
if (*StartOfFileName == OBJ_NAME_PATH_SEPARATOR) {
|
|
StartOfFileName++;
|
|
break;
|
|
}
|
|
|
|
StartOfFileName--;
|
|
}
|
|
|
|
//
|
|
// Validate that this is a legal FAT file name.
|
|
//
|
|
|
|
TargetFileName.Length = (USHORT)((PCHAR)EndOfFileName - (PCHAR)StartOfFileName);
|
|
TargetFileName.MaximumLength = TargetFileName.Length;
|
|
TargetFileName.Buffer = StartOfFileName;
|
|
|
|
if (!FatxIsValidFatFileName(&TargetFileName)) {
|
|
return STATUS_OBJECT_NAME_INVALID;
|
|
}
|
|
|
|
//
|
|
// Figure out which directory the file will be renamed into.
|
|
//
|
|
|
|
if (IrpSp->Parameters.SetFile.FileObject != NULL) {
|
|
TargetDirectoryFcb =
|
|
(PFAT_FCB)IrpSp->Parameters.SetFile.FileObject->FsContext;
|
|
} else {
|
|
TargetDirectoryFcb = Fcb->ParentFcb;
|
|
}
|
|
|
|
ASSERT(FatxIsFlagSet(TargetDirectoryFcb->Flags, FAT_FCB_DIRECTORY));
|
|
|
|
//
|
|
// Verify that the path doesn't exceed the length restictions.
|
|
//
|
|
|
|
NewPathNameLength = TargetDirectoryFcb->PathNameLength +
|
|
sizeof(OBJ_NAME_PATH_SEPARATOR) + TargetFileName.Length;
|
|
|
|
if (NewPathNameLength > FAT_PATH_NAME_LIMIT * sizeof(OCHAR)) {
|
|
return STATUS_OBJECT_NAME_INVALID;
|
|
}
|
|
|
|
//
|
|
// If this is a directory, then verify that there are no open files under
|
|
// this directory.
|
|
//
|
|
|
|
if (FatxIsFlagSet(Fcb->Flags, FAT_FCB_DIRECTORY) &&
|
|
!IsListEmpty(&Fcb->Directory.ChildFcbList)) {
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
//
|
|
// Check if the target file name already exists.
|
|
//
|
|
|
|
status = FatxLookupElementNameInDirectory(VolumeExtension, Irp,
|
|
TargetDirectoryFcb, &TargetFileName, &DirectoryEntry,
|
|
&DirectoryByteOffset, &EmptyDirectoryByteOffset);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
//
|
|
// The target file name already exists. Check if the caller allows us
|
|
// to replace an existing file and that we didn't find a directory or
|
|
// read-only file.
|
|
//
|
|
|
|
if (!RenameInformation->ReplaceIfExists ||
|
|
FatxIsFlagSet(DirectoryEntry.FileAttributes, FILE_ATTRIBUTE_DIRECTORY) ||
|
|
FatxIsFlagSet(DirectoryEntry.FileAttributes, FILE_ATTRIBUTE_READONLY)) {
|
|
return STATUS_OBJECT_NAME_COLLISION;
|
|
}
|
|
|
|
//
|
|
// Don't overwrite a file that's already open.
|
|
//
|
|
|
|
if (FatxFindOpenChildFcb(TargetDirectoryFcb, &TargetFileName,
|
|
&FoundOrNewFcb)) {
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
//
|
|
// Delete the existing file.
|
|
//
|
|
|
|
status = FatxDeleteFile(VolumeExtension, Irp, TargetDirectoryFcb,
|
|
DirectoryByteOffset);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// We can use the old file's directory byte offset as the byte offset
|
|
// for the renamed file, if needed.
|
|
//
|
|
|
|
EmptyDirectoryByteOffset = DirectoryByteOffset;
|
|
|
|
} else if (status != STATUS_OBJECT_NAME_NOT_FOUND) {
|
|
|
|
//
|
|
// Some unknown error was returned, so bail out now.
|
|
//
|
|
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// We'll need to replace the file name buffer that's currently stored in the
|
|
// file control block. Allocate a new string and copy the target file name
|
|
// into the buffer.
|
|
//
|
|
|
|
TargetFileNameBuffer = (POSTR)ExAllocatePoolWithTag(TargetFileName.Length,
|
|
'nFtF');
|
|
|
|
if (TargetFileNameBuffer == NULL) {
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlCopyMemory(TargetFileNameBuffer, TargetFileName.Buffer,
|
|
TargetFileName.Length);
|
|
|
|
//
|
|
// Check if we're renaming inside the same directory. If so, change the
|
|
// file name in the file control block and commit the change. If this
|
|
// fails, back out to the original file name.
|
|
//
|
|
|
|
if (TargetDirectoryFcb == Fcb->ParentFcb) {
|
|
|
|
Fcb->FileNameBuffer = TargetFileNameBuffer;
|
|
Fcb->FileNameLength = (UCHAR)TargetFileName.Length;
|
|
|
|
status = FatxUpdateDirectoryEntry(VolumeExtension, Irp, Fcb);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
//
|
|
// If the original file name buffer didn't come from the file
|
|
// control block itself, then free it now.
|
|
//
|
|
|
|
if (FatxIsFlagSet(Fcb->Flags, FAT_FCB_FREE_FILE_NAME_BUFFER)) {
|
|
ExFreePool(OriginalFileNameBuffer);
|
|
}
|
|
|
|
//
|
|
// We're going to keep the file name that we allocated, so set a
|
|
// flag that the close code can look at to know that the file name
|
|
// buffer must be freed.
|
|
//
|
|
|
|
Fcb->Flags |= FAT_FCB_FREE_FILE_NAME_BUFFER;
|
|
|
|
} else {
|
|
Fcb->FileNameBuffer = OriginalFileNameBuffer;
|
|
Fcb->FileNameLength = OriginalFileNameLength;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// We're moving a file across directories.
|
|
//
|
|
|
|
//
|
|
// If there isn't an empty directory entry, then we'll need to add
|
|
// another cluster to the directory.
|
|
//
|
|
|
|
if (EmptyDirectoryByteOffset == MAXULONG) {
|
|
|
|
//
|
|
// If we found the end of the directory stream, then we must have
|
|
// established how many bytes are allocated to the file.
|
|
//
|
|
|
|
ASSERT(TargetDirectoryFcb->AllocationSize != MAXULONG);
|
|
|
|
//
|
|
// The new directory empty will be placed at the start of the new
|
|
// extension.
|
|
//
|
|
|
|
EmptyDirectoryByteOffset = TargetDirectoryFcb->AllocationSize;
|
|
|
|
//
|
|
// Attempt to add another cluster to the directory's allocation.
|
|
//
|
|
|
|
status = FatxExtendDirectoryAllocation(VolumeExtension, Irp,
|
|
TargetDirectoryFcb);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
ExFreePool(TargetFileNameBuffer);
|
|
return status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Mark the directory entry as deleted. If the system goes down after
|
|
// successfully committing this change, then we've lost the file.
|
|
//
|
|
|
|
status = FatxMarkDirectoryEntryDeleted(VolumeExtension, Irp, Fcb);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
ExFreePool(TargetFileNameBuffer);
|
|
return status;
|
|
}
|
|
|
|
//
|
|
// Detach the file control block from its old parent.
|
|
//
|
|
|
|
RemoveEntryList(&Fcb->SiblingFcbLink);
|
|
FatxDereferenceFcb(Fcb->ParentFcb);
|
|
|
|
//
|
|
// Attach the file control block to its new parent.
|
|
//
|
|
|
|
TargetDirectoryFcb->ReferenceCount++;
|
|
Fcb->ParentFcb = TargetDirectoryFcb;
|
|
InsertHeadList(&TargetDirectoryFcb->Directory.ChildFcbList,
|
|
&Fcb->SiblingFcbLink);
|
|
|
|
//
|
|
// Update the file control block with its new file name and directory byte
|
|
// offset.
|
|
//
|
|
|
|
Fcb->FileNameBuffer = TargetFileNameBuffer;
|
|
Fcb->FileNameLength = (UCHAR)TargetFileName.Length;
|
|
Fcb->DirectoryByteOffset = EmptyDirectoryByteOffset;
|
|
|
|
status = FatxUpdateDirectoryEntry(VolumeExtension, Irp, Fcb);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
//
|
|
// Update the path name length stored in the file control block.
|
|
//
|
|
|
|
Fcb->PathNameLength = (UCHAR)NewPathNameLength;
|
|
|
|
//
|
|
// If the original file name buffer didn't come from the file control
|
|
// block itself, then free it now.
|
|
//
|
|
|
|
if (FatxIsFlagSet(Fcb->Flags, FAT_FCB_FREE_FILE_NAME_BUFFER)) {
|
|
ExFreePool(OriginalFileNameBuffer);
|
|
}
|
|
|
|
//
|
|
// We're going to keep the file name that we allocated, so set a flag
|
|
// that the close code can look at to know that the file name buffer
|
|
// must be freed.
|
|
//
|
|
|
|
Fcb->Flags |= FAT_FCB_FREE_FILE_NAME_BUFFER;
|
|
|
|
} else {
|
|
|
|
//
|
|
// If this commit fails, then assume that we're not going to have any
|
|
// hope of restoring the original state of the on-disk structures.
|
|
// Leave the file control block in a zombie state by disconnecting it
|
|
// from any parent directory.
|
|
//
|
|
|
|
FatxDbgPrint(("FATX: failed to move file across directories\n"));
|
|
|
|
Fcb->FileNameBuffer = OriginalFileNameBuffer;
|
|
Fcb->FileNameLength = OriginalFileNameLength;
|
|
|
|
RemoveEntryList(&Fcb->SiblingFcbLink);
|
|
FatxDereferenceFcb(TargetDirectoryFcb);
|
|
|
|
Fcb->ParentFcb = NULL;
|
|
|
|
ExFreePool(TargetFileNameBuffer);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
FatxSetDispositionInformation(
|
|
IN PFAT_VOLUME_EXTENSION VolumeExtension,
|
|
IN PIRP Irp,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PFILE_DISPOSITION_INFORMATION DispositionInformation
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine changes the disposition information of the supplied file
|
|
object.
|
|
|
|
Arguments:
|
|
|
|
VolumeExtension - Specifies the extension that the I/O request is for.
|
|
|
|
Irp - Specifies the packet that describes the I/O request.
|
|
|
|
FileObject - Specifies the file object to apply the information to.
|
|
|
|
DispositionInformation - Specifies the disposition information to apply to
|
|
the file.
|
|
|
|
Return Value:
|
|
|
|
Status of operation.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
PFAT_FCB Fcb;
|
|
|
|
Fcb = (PFAT_FCB)FileObject->FsContext;
|
|
|
|
ASSERT(FatxIsFlagClear(Fcb->Flags, FAT_FCB_VOLUME));
|
|
ASSERT(VolumeExtension->VolumeMutexExclusiveOwner == KeGetCurrentThread());
|
|
|
|
//
|
|
// If the caller doesn't want to delete the file, then clear the flags from
|
|
// the file control block and the file object.
|
|
//
|
|
|
|
if (!DispositionInformation->DeleteFile) {
|
|
|
|
Fcb->Flags &= ~FAT_FCB_DELETE_ON_CLOSE;
|
|
FileObject->DeletePending = FALSE;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// If this is a read only file, then it cannot be deleted.
|
|
//
|
|
|
|
if (FatxIsFlagSet(Fcb->FileAttributes, FILE_ATTRIBUTE_READONLY)) {
|
|
return STATUS_CANNOT_DELETE;
|
|
}
|
|
|
|
//
|
|
// If this is a directory, then check if the directory is empty. We'll also
|
|
// allow corrupt directories to be removed from the disk.
|
|
//
|
|
|
|
if (FatxIsFlagSet(Fcb->Flags, FAT_FCB_DIRECTORY)) {
|
|
|
|
status = FatxIsDirectoryEmpty(VolumeExtension, Irp, Fcb);
|
|
|
|
if ((status != STATUS_SUCCESS) && (status != STATUS_FILE_CORRUPT_ERROR)) {
|
|
return status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Mark the file as delete pending.
|
|
//
|
|
|
|
Fcb->Flags |= FAT_FCB_DELETE_ON_CLOSE;
|
|
FileObject->DeletePending = TRUE;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
FatxSetEndOfFileInformation(
|
|
IN PFAT_VOLUME_EXTENSION VolumeExtension,
|
|
IN PIRP Irp,
|
|
IN PFILE_OBJECT FileObject,
|
|
IN PFILE_END_OF_FILE_INFORMATION EndOfFileInformation
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine changes the end of file information of the supplied file
|
|
object.
|
|
|
|
Arguments:
|
|
|
|
VolumeExtension - Specifies the extension that the I/O request is for.
|
|
|
|
Irp - Specifies the packet that describes the I/O request.
|
|
|
|
FileObject - Specifies the file object to apply the information to.
|
|
|
|
EndOfFileInformation - Specifies the end of file information to apply to
|
|
the file.
|
|
|
|
Return Value:
|
|
|
|
Status of operation.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
PFAT_FCB FileFcb;
|
|
ULONG NewFileSize;
|
|
ULONG OriginalFileSize;
|
|
|
|
FileFcb = (PFAT_FCB)FileObject->FsContext;
|
|
|
|
ASSERT(FatxIsFlagClear(FileFcb->Flags, FAT_FCB_VOLUME | FAT_FCB_DIRECTORY));
|
|
ASSERT(VolumeExtension->VolumeMutexExclusiveOwner == KeGetCurrentThread());
|
|
ASSERT(FileFcb->File.FileMutexExclusiveOwner == KeGetCurrentThread());
|
|
|
|
//
|
|
// Validate that the upper 32-bits of the end of file are zero.
|
|
//
|
|
|
|
if (EndOfFileInformation->EndOfFile.HighPart != 0) {
|
|
return STATUS_DISK_FULL;
|
|
}
|
|
|
|
//
|
|
// If the new file size and the current file size match, then there's
|
|
// nothing to do.
|
|
//
|
|
|
|
NewFileSize = EndOfFileInformation->EndOfFile.LowPart;
|
|
OriginalFileSize = FileFcb->FileSize;
|
|
|
|
if (NewFileSize == OriginalFileSize) {
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// If the new file size is beyond the current file size, then we might need
|
|
// to extend the file allocation. Note that we want to make sure that we
|
|
// don't truncate the allocation here if the end of file is less than the
|
|
// allocated size.
|
|
//
|
|
|
|
if (NewFileSize > OriginalFileSize) {
|
|
|
|
status = FatxSetAllocationSize(VolumeExtension, Irp, FileFcb,
|
|
NewFileSize, FALSE, TRUE);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
return status;
|
|
}
|
|
|
|
ASSERT(FileFcb->FileSize == OriginalFileSize);
|
|
}
|
|
|
|
//
|
|
// Change the file size in the file control block to the requested file
|
|
// size.
|
|
//
|
|
|
|
FileFcb->FileSize = NewFileSize;
|
|
|
|
//
|
|
// Commit the directory change. If this fails, then back out the changes to
|
|
// the file control block so that we still mirror the on-disk structure.
|
|
//
|
|
|
|
status = FatxUpdateDirectoryEntry(VolumeExtension, Irp, FileFcb);
|
|
|
|
if (!NT_SUCCESS(status)) {
|
|
FileFcb->FileSize = OriginalFileSize;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
NTSTATUS
|
|
FatxFsdSetInformation(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called by the I/O manager to handle IRP_MJ_SET_INFORMATION
|
|
requests.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Specifies the device object that the I/O request is for.
|
|
|
|
Irp - Specifies the packet that describes the I/O request.
|
|
|
|
Return Value:
|
|
|
|
Status of operation.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
PFAT_VOLUME_EXTENSION VolumeExtension;
|
|
PIO_STACK_LOCATION IrpSp;
|
|
PFILE_OBJECT FileObject;
|
|
PFAT_FCB Fcb;
|
|
PFILE_POSITION_INFORMATION PositionInformation;
|
|
PFILE_ALLOCATION_INFORMATION AllocationInformation;
|
|
|
|
VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension;
|
|
IrpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
FileObject = IrpSp->FileObject;
|
|
Fcb = (PFAT_FCB)FileObject->FsContext;
|
|
|
|
//
|
|
// If this is a file control block for a standard file, then we need to
|
|
// acquire the file's mutex in order to synchronize access to the Flags
|
|
// field of the file control block.
|
|
//
|
|
|
|
if (FatxIsFlagClear(Fcb->Flags, FAT_FCB_VOLUME | FAT_FCB_DIRECTORY)) {
|
|
FatxAcquireFileMutexExclusive(Fcb);
|
|
}
|
|
|
|
FatxAcquireVolumeMutexExclusive(VolumeExtension);
|
|
|
|
//
|
|
// Check if the volume has been dismounted.
|
|
//
|
|
|
|
if (FatxIsFlagSet(VolumeExtension->Flags, FAT_VOLUME_DISMOUNTED)) {
|
|
status = STATUS_VOLUME_DISMOUNTED;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// Check if the file object has already been cleaned up. We don't allow a
|
|
// a file object to be accessed after its handle has been closed.
|
|
//
|
|
|
|
if (FatxIsFlagSet(FileObject->Flags, FO_CLEANUP_COMPLETE)) {
|
|
status = STATUS_FILE_CLOSED;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// Dispatch the information class function.
|
|
//
|
|
|
|
status = STATUS_SUCCESS;
|
|
|
|
switch (IrpSp->Parameters.QueryFile.FileInformationClass) {
|
|
|
|
case FileBasicInformation:
|
|
//
|
|
// We can't change the time stamps or the file attributes for the
|
|
// volume or the root directory.
|
|
//
|
|
|
|
if (FatxIsFlagSet(Fcb->Flags, FAT_FCB_VOLUME) ||
|
|
FatxIsFlagSet(Fcb->Flags, FAT_FCB_ROOT_DIRECTORY)) {
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
status = FatxSetBasicInformation(VolumeExtension, Irp, FileObject,
|
|
(PFILE_BASIC_INFORMATION)Irp->UserBuffer);
|
|
break;
|
|
|
|
case FileRenameInformation:
|
|
//
|
|
// We can't rename the volume or the root directory.
|
|
//
|
|
|
|
if (FatxIsFlagSet(Fcb->Flags, FAT_FCB_VOLUME) ||
|
|
FatxIsFlagSet(Fcb->Flags, FAT_FCB_ROOT_DIRECTORY)) {
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
status = FatxSetRenameInformation(VolumeExtension, Irp);
|
|
break;
|
|
|
|
case FileDispositionInformation:
|
|
//
|
|
// We can't delete the volume or the root directory.
|
|
//
|
|
|
|
if (FatxIsFlagSet(Fcb->Flags, FAT_FCB_VOLUME) ||
|
|
FatxIsFlagSet(Fcb->Flags, FAT_FCB_ROOT_DIRECTORY)) {
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
status = FatxSetDispositionInformation(VolumeExtension, Irp, FileObject,
|
|
(PFILE_DISPOSITION_INFORMATION)Irp->UserBuffer);
|
|
break;
|
|
|
|
case FilePositionInformation:
|
|
PositionInformation = (PFILE_POSITION_INFORMATION)Irp->UserBuffer;
|
|
|
|
//
|
|
// If the file was opened without intermediate buffering, then the
|
|
// byte offset must be sector aligned.
|
|
//
|
|
|
|
if (FatxIsFlagSet(FileObject->Flags, FO_NO_INTERMEDIATE_BUFFERING) &&
|
|
!FatxIsSectorAligned(VolumeExtension,
|
|
PositionInformation->CurrentByteOffset.LowPart)) {
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
FileObject->CurrentByteOffset = PositionInformation->CurrentByteOffset;
|
|
break;
|
|
|
|
case FileAllocationInformation:
|
|
AllocationInformation =
|
|
(PFILE_ALLOCATION_INFORMATION)Irp->UserBuffer;
|
|
|
|
//
|
|
// We can't change the allocation size for the volume or any
|
|
// directory.
|
|
//
|
|
|
|
if (FatxIsFlagSet(Fcb->Flags, FAT_FCB_VOLUME) ||
|
|
FatxIsFlagSet(Fcb->Flags, FAT_FCB_DIRECTORY)) {
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
//
|
|
// Validate that the upper 32-bits of the allocation size are zero.
|
|
//
|
|
|
|
if (AllocationInformation->AllocationSize.HighPart != 0) {
|
|
status = STATUS_DISK_FULL;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
status = FatxSetAllocationSize(VolumeExtension, Irp, Fcb,
|
|
AllocationInformation->AllocationSize.LowPart, FALSE, FALSE);
|
|
break;
|
|
|
|
case FileEndOfFileInformation:
|
|
//
|
|
// We can't change the end of file for the volume or any directory.
|
|
//
|
|
|
|
if (FatxIsFlagSet(Fcb->Flags, FAT_FCB_VOLUME) ||
|
|
FatxIsFlagSet(Fcb->Flags, FAT_FCB_DIRECTORY)) {
|
|
status = STATUS_INVALID_PARAMETER;
|
|
goto CleanupAndExit;
|
|
}
|
|
|
|
status = FatxSetEndOfFileInformation(VolumeExtension, Irp, FileObject,
|
|
(PFILE_END_OF_FILE_INFORMATION)Irp->UserBuffer);
|
|
break;
|
|
|
|
default:
|
|
status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
CleanupAndExit:
|
|
FatxReleaseVolumeMutex(VolumeExtension);
|
|
|
|
if (FatxIsFlagClear(Fcb->Flags, FAT_FCB_VOLUME | FAT_FCB_DIRECTORY)) {
|
|
FatxReleaseFileMutex(Fcb);
|
|
}
|
|
|
|
Irp->IoStatus.Status = status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
|
|
return status;
|
|
}
|