diff --git a/inc/winfsp/fsctl.h b/inc/winfsp/fsctl.h index ae62e389..a95bd926 100644 --- a/inc/winfsp/fsctl.h +++ b/inc/winfsp/fsctl.h @@ -67,7 +67,7 @@ enum FspFsctlTransactFlushBuffersKind, FspFsctlTransactQueryVolumeInformationKind, FspFsctlTransactSetVolumeInformationKind, - FspFsctlTransactDirectoryControlKind, + FspFsctlTransactQueryDirectoryKind, FspFsctlTransactFileSystemControlKind, FspFsctlTransactDeviceControlKind, FspFsctlTransactShutdownKind, @@ -135,6 +135,13 @@ typedef struct UINT64 IndexNumber; } FSP_FSCTL_FILE_INFO; typedef struct +{ + UINT16 Size; + FSP_FSCTL_FILE_INFO FileInfo; + UINT64 NextOffset; + WCHAR FileNameBuf[]; +} FSP_FSCTL_DIR_INFO; +typedef struct { UINT16 Offset; UINT16 Size; @@ -248,6 +255,14 @@ typedef struct } Info; } SetVolumeInformation; struct + { + UINT64 UserContext; + UINT64 UserContext2; + UINT64 Address; + UINT64 Offset; + UINT32 Length; + } QueryDirectory; + struct { UINT64 UserContext; UINT64 UserContext2; @@ -261,7 +276,7 @@ typedef struct FSP_FSCTL_TRANSACT_BUF SecurityDescriptor; } SetSecurity; } Req; - FSP_FSCTL_TRANSACT_BUF FileName; /* {Create,Cleanup,SetInformation/{Disposition,Rename}} */ + FSP_FSCTL_TRANSACT_BUF FileName; /* {Create,Cleanup,SetInformation/{...},QueryDirectory} */ FSP_FSCTL_DECLSPEC_ALIGN UINT8 Buffer[]; } FSP_FSCTL_TRANSACT_REQ; typedef struct diff --git a/inc/winfsp/winfsp.h b/inc/winfsp/winfsp.h index be919eb3..80912d58 100644 --- a/inc/winfsp/winfsp.h +++ b/inc/winfsp/winfsp.h @@ -104,6 +104,10 @@ typedef struct _FSP_FILE_SYSTEM_INTERFACE FSP_FSCTL_TRANSACT_REQ *Request, PVOID FileNode, SECURITY_INFORMATION SecurityInformation, PSECURITY_DESCRIPTOR SecurityDescriptor); + NTSTATUS (*ReadDirectory)(FSP_FILE_SYSTEM *FileSystem, + FSP_FSCTL_TRANSACT_REQ *Request, + PVOID FileNode, PVOID Buffer, UINT64 Offset, ULONG Length, + PULONG PBytesTransferred); } FSP_FILE_SYSTEM_INTERFACE; typedef struct _FSP_FILE_SYSTEM { @@ -196,6 +200,8 @@ FSP_API NTSTATUS FspFileSystemOpQueryVolumeInformation(FSP_FILE_SYSTEM *FileSyst FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response); FSP_API NTSTATUS FspFileSystemOpSetVolumeInformation(FSP_FILE_SYSTEM *FileSystem, FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response); +FSP_API NTSTATUS FspFileSystemOpQueryDirectory(FSP_FILE_SYSTEM *FileSystem, + FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response); FSP_API NTSTATUS FspFileSystemOpQuerySecurity(FSP_FILE_SYSTEM *FileSystem, FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response); FSP_API NTSTATUS FspFileSystemOpSetSecurity(FSP_FILE_SYSTEM *FileSystem, diff --git a/src/dll/dispatch.c b/src/dll/dispatch.c index 5b8ece18..37f85339 100644 --- a/src/dll/dispatch.c +++ b/src/dll/dispatch.c @@ -51,6 +51,7 @@ FSP_API NTSTATUS FspFileSystemCreate(PWSTR DevicePath, FileSystem->Operations[FspFsctlTransactSetInformationKind] = FspFileSystemOpSetInformation; FileSystem->Operations[FspFsctlTransactQueryVolumeInformationKind] = FspFileSystemOpQueryVolumeInformation; FileSystem->Operations[FspFsctlTransactSetVolumeInformationKind] = FspFileSystemOpSetVolumeInformation; + FileSystem->Operations[FspFsctlTransactQueryDirectoryKind] = FspFileSystemOpQueryDirectory; FileSystem->Operations[FspFsctlTransactQuerySecurityKind] = FspFileSystemOpQuerySecurity; FileSystem->Operations[FspFsctlTransactSetSecurityKind] = FspFileSystemOpSetSecurity; FileSystem->Interface = Interface; diff --git a/src/dll/fsop.c b/src/dll/fsop.c index 81c0a913..f12aeb4d 100644 --- a/src/dll/fsop.c +++ b/src/dll/fsop.c @@ -597,6 +597,12 @@ FSP_API NTSTATUS FspFileSystemOpSetVolumeInformation(FSP_FILE_SYSTEM *FileSystem return STATUS_SUCCESS; } +FSP_API NTSTATUS FspFileSystemOpQueryDirectory(FSP_FILE_SYSTEM *FileSystem, + FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response) +{ + return STATUS_INVALID_DEVICE_REQUEST; +} + FSP_API NTSTATUS FspFileSystemOpQuerySecurity(FSP_FILE_SYSTEM *FileSystem, FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response) { diff --git a/src/sys/device.c b/src/sys/device.c index 8bee9609..52c4967e 100644 --- a/src/sys/device.c +++ b/src/sys/device.c @@ -290,7 +290,7 @@ static NTSTATUS FspFsvolDeviceInit(PDEVICE_OBJECT DeviceObject) NTSTATUS Result; FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = FspFsvolDeviceExtension(DeviceObject); LARGE_INTEGER IrpTimeout; - LARGE_INTEGER MetaTimeout; + LARGE_INTEGER SecurityTimeout, DirInfoTimeout; /* * Volume device initialization is a mess, because of the different ways of @@ -327,15 +327,25 @@ static NTSTATUS FspFsvolDeviceInit(PDEVICE_OBJECT DeviceObject) FsvolDeviceExtension->InitDoneIoq = 1; /* create our security meta cache */ - MetaTimeout.QuadPart = FspTimeoutFromMillis(FsvolDeviceExtension->VolumeParams.FileInfoTimeout); + SecurityTimeout.QuadPart = FspTimeoutFromMillis(FsvolDeviceExtension->VolumeParams.FileInfoTimeout); /* convert millis to nanos */ Result = FspMetaCacheCreate( - FspFsvolDeviceSecurityCacheCapacity, FspFsvolDeviceSecurityCacheItemSizeMax, &MetaTimeout, + FspFsvolDeviceSecurityCacheCapacity, FspFsvolDeviceSecurityCacheItemSizeMax, &SecurityTimeout, &FsvolDeviceExtension->SecurityCache); if (!NT_SUCCESS(Result)) return Result; FsvolDeviceExtension->InitDoneSec = 1; + /* create our directory meta cache */ + DirInfoTimeout.QuadPart = FspTimeoutFromMillis(FsvolDeviceExtension->VolumeParams.FileInfoTimeout); + /* convert millis to nanos */ + Result = FspMetaCacheCreate( + FspFsvolDeviceDirInfoCacheCapacity, FspFsvolDeviceDirInfoCacheItemSizeMax, &DirInfoTimeout, + &FsvolDeviceExtension->DirInfoCache); + if (!NT_SUCCESS(Result)) + return Result; + FsvolDeviceExtension->InitDoneDir = 1; + /* initialize our context table */ ExInitializeResourceLite(&FsvolDeviceExtension->FileRenameResource); ExInitializeResourceLite(&FsvolDeviceExtension->ContextTableResource); @@ -385,6 +395,10 @@ static VOID FspFsvolDeviceFini(PDEVICE_OBJECT DeviceObject) if (FsvolDeviceExtension->InitDoneTimer) IoStopTimer(DeviceObject); + /* delete the directory meta cache */ + if (FsvolDeviceExtension->InitDoneDir) + FspMetaCacheDelete(FsvolDeviceExtension->DirInfoCache); + /* delete the security meta cache */ if (FsvolDeviceExtension->InitDoneSec) FspMetaCacheDelete(FsvolDeviceExtension->SecurityCache); @@ -462,6 +476,7 @@ static VOID FspFsvolDeviceExpirationRoutine(PVOID Context) InterruptTime = KeQueryInterruptTime(); FspMetaCacheInvalidateExpired(FsvolDeviceExtension->SecurityCache, InterruptTime); + FspMetaCacheInvalidateExpired(FsvolDeviceExtension->DirInfoCache, InterruptTime); FspIoqRemoveExpired(FsvolDeviceExtension->Ioq, InterruptTime); KeAcquireSpinLock(&FsvolDeviceExtension->ExpirationLock, &Irql); diff --git a/src/sys/dirctl.c b/src/sys/dirctl.c index b871128a..fef2adb1 100644 --- a/src/sys/dirctl.c +++ b/src/sys/dirctl.c @@ -6,25 +6,388 @@ #include +static NTSTATUS FspFsvolQueryDirectory( + PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp); +static NTSTATUS FspFsvolQueryDirectoryRetry( + PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp, + BOOLEAN CanWait); +static NTSTATUS FspFsvolQueryDirectoryCopy( + FILE_INFORMATION_CLASS FileInformationClass, BOOLEAN ReturnSingleEntry, + PUNICODE_STRING QueryFileName, BOOLEAN CaseInsensitive, + PVOID DestBuf, PULONG PDestLen, + FSP_FSCTL_DIR_INFO *DirInfo, ULONG DirInfoSize); +static NTSTATUS FspFsvolNotifyChangeDirectory( + PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp); static NTSTATUS FspFsvolDirectoryControl( - PDEVICE_OBJECT DeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp); + PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp); +FSP_IOPREP_DISPATCH FspFsvolDirectoryControlPrepare; FSP_IOCMPL_DISPATCH FspFsvolDirectoryControlComplete; +static FSP_IOP_REQUEST_FINI FspFsvolQueryDirectoryRequestFini; FSP_DRIVER_DISPATCH FspDirectoryControl; #ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, FspFsvolQueryDirectory) +#pragma alloc_text(PAGE, FspFsvolQueryDirectoryRetry) +#pragma alloc_text(PAGE, FspFsvolQueryDirectoryCopy) +#pragma alloc_text(PAGE, FspFsvolNotifyChangeDirectory) #pragma alloc_text(PAGE, FspFsvolDirectoryControl) +#pragma alloc_text(PAGE, FspFsvolDirectoryControlPrepare) #pragma alloc_text(PAGE, FspFsvolDirectoryControlComplete) +#pragma alloc_text(PAGE, FspFsvolQueryDirectoryRequestFini) #pragma alloc_text(PAGE, FspDirectoryControl) #endif -static NTSTATUS FspFsvolDirectoryControl( - PDEVICE_OBJECT DeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp) +enum +{ + /* QueryDirectory */ + RequestFileNode = 0, + RequestDirectoryChangeNumber = 1, +}; + +static NTSTATUS FspFsvolQueryDirectory( + PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp) +{ + PAGED_CODE(); + + /* is this a valid FileObject? */ + if (!FspFileNodeIsValid(IrpSp->FileObject->FsContext)) + return STATUS_INVALID_DEVICE_REQUEST; + + NTSTATUS Result; + PFILE_OBJECT FileObject = IrpSp->FileObject; + FSP_FILE_NODE *FileNode = FileObject->FsContext; + FILE_INFORMATION_CLASS FileInformationClass = IrpSp->Parameters.QueryDirectory.FileInformationClass; + PUNICODE_STRING FileName = IrpSp->Parameters.QueryDirectory.FileName; + + /* only directory files can be queried */ + if (!FileNode->IsDirectory) + return STATUS_INVALID_PARAMETER; + + /* check that FileName is valid (if supplied) */ + if (0 != FileName && !FspUnicodePathIsValid(FileName, FALSE)) + return STATUS_INVALID_PARAMETER; + + /* is this an allowed file information class? */ + switch (FileInformationClass) + { + case FileDirectoryInformation: + case FileFullDirectoryInformation: + case FileIdFullDirectoryInformation: + case FileNamesInformation: + case FileBothDirectoryInformation: + case FileIdBothDirectoryInformation: + break; + default: + return STATUS_INVALID_INFO_CLASS; + } + + Result = FspFsvolQueryDirectoryRetry(FsvolDeviceObject, Irp, IrpSp, IoIsOperationSynchronous(Irp)); + + return Result; +} + +static NTSTATUS FspFsvolQueryDirectoryRetry( + PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp, + BOOLEAN CanWait) +{ + PAGED_CODE(); + + NTSTATUS Result; + PFILE_OBJECT FileObject = IrpSp->FileObject; + FSP_FILE_NODE *FileNode = FileObject->FsContext; + FSP_FILE_DESC *FileDesc = FileObject->FsContext2; + BOOLEAN RestartScan = BooleanFlagOn(Irp->Flags, SL_RESTART_SCAN); + BOOLEAN IndexSpecified = BooleanFlagOn(Irp->Flags, SL_INDEX_SPECIFIED); + BOOLEAN ReturnSingleEntry = BooleanFlagOn(Irp->Flags, SL_RETURN_SINGLE_ENTRY); + FILE_INFORMATION_CLASS FileInformationClass = IrpSp->Parameters.QueryDirectory.FileInformationClass; + PUNICODE_STRING FileName = IrpSp->Parameters.QueryDirectory.FileName; + PVOID Buffer = 0 != Irp->AssociatedIrp.SystemBuffer ? + Irp->AssociatedIrp.SystemBuffer : Irp->UserBuffer; + ULONG Length = IrpSp->Parameters.QueryDirectory.Length; + PVOID DirInfoBuffer; + ULONG DirInfoSize; + FSP_FSCTL_TRANSACT_REQ *Request; + BOOLEAN InitialQuery; + BOOLEAN Success; + + ASSERT(FileNode == FileDesc->FileNode); + + /* try to acquire the FileNode exclusive; Full because we may need to send a Request */ + Success = DEBUGTEST(90, TRUE) && + FspFileNodeTryAcquireExclusiveF(FileNode, FspFileNodeAcquireFull, CanWait); + if (!Success) + return FspWqRepostIrpWorkItem(Irp, FspFsvolQueryDirectoryRetry, 0); + + InitialQuery = 0 == FileDesc->QueryFileName.Buffer; + + /* set the QueryFileName in the FileDesc */ + Result = FspFileDescResetQueryFileName(FileDesc, FileName, RestartScan); + if (!NT_SUCCESS(Result)) + { + FspFileNodeRelease(FileNode, Full); + return Result; + } + + /* determine where to (re)start */ + if (IndexSpecified) + FileDesc->QueryOffset = (UINT64)IrpSp->Parameters.QueryDirectory.FileIndex << 32; + else if (RestartScan) + FileDesc->QueryOffset = 0; + + FspFileNodeConvertExclusiveToShared(FileNode, Full); + + /* see if the required information is still in the cache and valid! */ + if (FspFileNodeReferenceDirInfo(FileNode, &DirInfoBuffer, &DirInfoSize)) + { + FspFileNodeRelease(FileNode, Full); + + FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = FspFsvolDeviceExtension(FsvolDeviceObject); + BOOLEAN CaseInsensitive = 0 == FsvolDeviceExtension->VolumeParams.CaseSensitiveSearch; + + Result = FspFsvolQueryDirectoryCopy(FileInformationClass, ReturnSingleEntry, + &FileDesc->QueryFileName, CaseInsensitive, + Buffer, &Length, DirInfoBuffer, DirInfoSize); + FspFileNodeDereferenceDirInfo(DirInfoBuffer); + + if (NT_SUCCESS(Result) && 0 == Length) + Result = InitialQuery ? STATUS_NO_SUCH_FILE : STATUS_NO_MORE_FILES; + + Irp->IoStatus.Information = Length; + return Result; + } + + /* buffer the user buffer! */ + Result = FspBufferUserBuffer(Irp, Length, IoWriteAccess); + if (!NT_SUCCESS(Result)) + { + FspFileNodeRelease(FileNode, Full); + return Result; + } + + /* create request */ + Result = FspIopCreateRequestEx(Irp, 0, 0, FspFsvolQueryDirectoryRequestFini, &Request); + if (!NT_SUCCESS(Result)) + { + FspFileNodeRelease(FileNode, Full); + return Result; + } + + Request->Kind = FspFsctlTransactQueryDirectoryKind; + Request->Req.QueryDirectory.UserContext = FileNode->UserContext; + Request->Req.QueryDirectory.UserContext2 = FileDesc->UserContext2; + + FspFileNodeSetOwner(FileNode, Full, Request); + FspIopRequestContext(Request, RequestFileNode) = FileNode; + + return FSP_STATUS_IOQ_POST; +} + +static NTSTATUS FspFsvolQueryDirectoryCopy( + FILE_INFORMATION_CLASS FileInformationClass, BOOLEAN ReturnSingleEntry, + PUNICODE_STRING QueryFileName, BOOLEAN CaseInsensitive, + PVOID DestBuf, PULONG PDestLen, + FSP_FSCTL_DIR_INFO *DirInfo, ULONG DirInfoSize) +{ +#define FILL_INFO(Info, DirInfo)\ + Info->NextEntryOffset = 0;\ + Info->FileIndex = DirInfo->NextOffset >> 32;\ + Info->CreationTime.QuadPart = DirInfo->FileInfo.CreationTime;\ + Info->LastAccessTime.QuadPart = DirInfo->FileInfo.LastAccessTime;\ + Info->LastWriteTime.QuadPart = DirInfo->FileInfo.LastWriteTime;\ + Info->ChangeTime.QuadPart = DirInfo->FileInfo.ChangeTime;\ + Info->EndOfFile.QuadPart = DirInfo->FileInfo.FileSize;\ + Info->AllocationSize.QuadPart = DirInfo->FileInfo.AllocationSize;\ + Info->FileAttributes = 0 != DirInfo->FileInfo.FileAttributes ?\ + DirInfo->FileInfo.FileAttributes : FILE_ATTRIBUTE_NORMAL;\ + Info->FileNameLength = FileNameLen;\ + RtlCopyMemory(Info->FileName, DirInfo->FileNameBuf, FileNameLen) + + PAGED_CODE(); + + BOOLEAN MatchAll = FspFileDescQueryFileNameMatchAll == QueryFileName->Buffer; + PVOID PrevDestBuf = 0; + PUINT8 DestBufBgn = (PUINT8)DestBuf; + PUINT8 DestBufEnd = (PUINT8)DestBuf + *PDestLen; + PUINT8 DirInfoEnd = (PUINT8)DirInfo + DirInfoSize; + ULONG BaseInfoLen, FileNameLen; + UNICODE_STRING FileName; + + *PDestLen = 0; + + switch (FileInformationClass) + { + case FileDirectoryInformation: + BaseInfoLen = FIELD_OFFSET(FILE_DIRECTORY_INFORMATION, FileName); + break; + case FileFullDirectoryInformation: + BaseInfoLen = FIELD_OFFSET(FILE_FULL_DIR_INFORMATION, FileName); + break; + case FileIdFullDirectoryInformation: + BaseInfoLen = FIELD_OFFSET(FILE_ID_FULL_DIR_INFORMATION, FileName); + break; + case FileNamesInformation: + BaseInfoLen = FIELD_OFFSET(FILE_NAMES_INFORMATION, FileName); + break; + case FileBothDirectoryInformation: + BaseInfoLen = FIELD_OFFSET(FILE_BOTH_DIR_INFORMATION, FileName); + break; + case FileIdBothDirectoryInformation: + BaseInfoLen = FIELD_OFFSET(FILE_ID_BOTH_DIR_INFORMATION, FileName); + break; + default: + return STATUS_INVALID_INFO_CLASS; + } + + try + { + for (;;) + { + if ((PUINT8)DirInfo + sizeof(DirInfo->Size) > DirInfoEnd || + sizeof(FSP_FSCTL_DIR_INFO) > DirInfo->Size) + break; + + FileNameLen = DirInfo->Size - sizeof(FSP_FSCTL_DIR_INFO); + FileName.Length = FileName.MaximumLength = (USHORT)FileNameLen; + FileName.Buffer = DirInfo->FileNameBuf; + + if (MatchAll || FsRtlIsNameInExpression(QueryFileName, &FileName, CaseInsensitive, 0)) + { + if ((PUINT8)DestBuf + FSP_FSCTL_ALIGN_UP(BaseInfoLen + FileNameLen, sizeof(LONGLONG)) > + DestBufEnd) + { + if (DestBufBgn == DestBuf) + { + *PDestLen = BaseInfoLen + FileNameLen; + return STATUS_BUFFER_OVERFLOW; + } + break; + } + + if (0 != PrevDestBuf) + *(PULONG)PrevDestBuf = (ULONG)((PUINT8)DestBuf - DestBufBgn); + PrevDestBuf = DestBuf; + + switch (FileInformationClass) + { + case FileDirectoryInformation: + { + FILE_DIRECTORY_INFORMATION *Info = DestBuf; + + FILL_INFO(Info, DirInfo); + } + break; + case FileFullDirectoryInformation: + { + FILE_FULL_DIR_INFORMATION *Info = DestBuf; + + FILL_INFO(Info, DirInfo); + Info->EaSize = 0; + } + break; + case FileIdFullDirectoryInformation: + { + FILE_ID_FULL_DIR_INFORMATION *Info = DestBuf; + + FILL_INFO(Info, DirInfo); + Info->EaSize = 0; + Info->FileId.QuadPart = DirInfo->FileInfo.IndexNumber; + } + break; + case FileNamesInformation: + { + FILE_NAMES_INFORMATION *Info = DestBuf; + + Info->NextEntryOffset = 0; + Info->FileIndex = DirInfo->NextOffset >> 32; + Info->FileNameLength = FileNameLen; + RtlCopyMemory(Info->FileName, DirInfo->FileNameBuf, FileNameLen); + } + break; + case FileBothDirectoryInformation: + { + FILE_BOTH_DIR_INFORMATION *Info = DestBuf; + + FILL_INFO(Info, DirInfo); + Info->EaSize = 0; + Info->ShortNameLength = 0; + RtlZeroMemory(Info->ShortName, sizeof Info->ShortName); + } + break; + case FileIdBothDirectoryInformation: + { + FILE_ID_BOTH_DIR_INFORMATION *Info = DestBuf; + + FILL_INFO(Info, DirInfo); + Info->EaSize = 0; + Info->ShortNameLength = 0; + RtlZeroMemory(Info->ShortName, sizeof Info->ShortName); + Info->FileId.QuadPart = DirInfo->FileInfo.IndexNumber; + } + break; + default: + ASSERT(0); + return STATUS_INVALID_INFO_CLASS; + } + + *PDestLen = (ULONG)((PUINT8)DestBuf + BaseInfoLen + FileNameLen - DestBufBgn); + + if (ReturnSingleEntry) + break; + + DestBuf = (PVOID)((PUINT8)DestBuf + + FSP_FSCTL_ALIGN_UP(BaseInfoLen + FileNameLen, sizeof(LONGLONG))); + } + + DirInfo = (PVOID)((PUINT8)DirInfo + FSP_FSCTL_DEFAULT_ALIGN_UP(DirInfo->Size)); + } + } + except (EXCEPTION_EXECUTE_HANDLER) + { + NTSTATUS Result = GetExceptionCode(); + if (STATUS_INSUFFICIENT_RESOURCES == Result) + return Result; + return FsRtlIsNtstatusExpected(Result) ? STATUS_INVALID_USER_BUFFER : Result; + } + + return STATUS_SUCCESS; +#undef FILL_INFO +} + +static NTSTATUS FspFsvolNotifyChangeDirectory( + PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp) { PAGED_CODE(); return STATUS_INVALID_DEVICE_REQUEST; } +static NTSTATUS FspFsvolDirectoryControl( + PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp) +{ + PAGED_CODE(); + + NTSTATUS Result = STATUS_INVALID_DEVICE_REQUEST; + switch (IrpSp->MinorFunction) + { + case IRP_MN_QUERY_DIRECTORY: + Result = FspFsvolQueryDirectory(FsvolDeviceObject, Irp, IrpSp); + break; + case IRP_MN_NOTIFY_CHANGE_DIRECTORY: + Result = FspFsvolNotifyChangeDirectory(FsvolDeviceObject, Irp, IrpSp); + break; + } + + return Result; +} + +NTSTATUS FspFsvolDirectoryControlPrepare( + PIRP Irp, FSP_FSCTL_TRANSACT_REQ *Request) +{ + PAGED_CODE(); + + return STATUS_SUCCESS; +} + NTSTATUS FspFsvolDirectoryControlComplete( PIRP Irp, const FSP_FSCTL_TRANSACT_RSP *Response) { @@ -33,6 +396,16 @@ NTSTATUS FspFsvolDirectoryControlComplete( FSP_LEAVE_IOC("%s", ""); } +static VOID FspFsvolQueryDirectoryRequestFini(FSP_FSCTL_TRANSACT_REQ *Request, PVOID Context[4]) +{ + PAGED_CODE(); + + FSP_FILE_NODE *FileNode = Context[RequestFileNode]; + + if (0 != FileNode) + FspFileNodeReleaseOwner(FileNode, Full, Request); +} + NTSTATUS FspDirectoryControl( PDEVICE_OBJECT DeviceObject, PIRP Irp) { diff --git a/src/sys/driver.c b/src/sys/driver.c index f7339caa..0e2d97d9 100644 --- a/src/sys/driver.c +++ b/src/sys/driver.c @@ -85,6 +85,7 @@ NTSTATUS DriverEntry( FspIopCompleteFunction[IRP_MJ_FLUSH_BUFFERS] = FspFsvolFlushBuffersComplete; FspIopCompleteFunction[IRP_MJ_QUERY_VOLUME_INFORMATION] = FspFsvolQueryVolumeInformationComplete; FspIopCompleteFunction[IRP_MJ_SET_VOLUME_INFORMATION] = FspFsvolSetVolumeInformationComplete; + FspIopPrepareFunction[IRP_MJ_DIRECTORY_CONTROL] = FspFsvolDirectoryControlPrepare; FspIopCompleteFunction[IRP_MJ_DIRECTORY_CONTROL] = FspFsvolDirectoryControlComplete; FspIopCompleteFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = FspFsvolFileSystemControlComplete; FspIopCompleteFunction[IRP_MJ_DEVICE_CONTROL] = FspFsvolDeviceControlComplete; diff --git a/src/sys/driver.h b/src/sys/driver.h index d419652f..0a9a7c18 100644 --- a/src/sys/driver.h +++ b/src/sys/driver.h @@ -268,6 +268,7 @@ FSP_IOCMPL_DISPATCH FspFsvolCloseComplete; FSP_IOPREP_DISPATCH FspFsvolCreatePrepare; FSP_IOCMPL_DISPATCH FspFsvolCreateComplete; FSP_IOCMPL_DISPATCH FspFsvolDeviceControlComplete; +FSP_IOPREP_DISPATCH FspFsvolDirectoryControlPrepare; FSP_IOCMPL_DISPATCH FspFsvolDirectoryControlComplete; FSP_IOCMPL_DISPATCH FspFsvolFileSystemControlComplete; FSP_IOCMPL_DISPATCH FspFsvolFlushBuffersComplete; @@ -624,6 +625,8 @@ enum { FspFsvolDeviceSecurityCacheCapacity = 100, FspFsvolDeviceSecurityCacheItemSizeMax = 4096, + FspFsvolDeviceDirInfoCacheCapacity = 100, + FspFsvolDeviceDirInfoCacheItemSizeMax = 16384, }; typedef struct { @@ -660,8 +663,8 @@ typedef struct typedef struct { FSP_DEVICE_EXTENSION Base; - UINT32 InitDoneFsvrt:1, InitDoneDelRsc:1, InitDoneIoq:1, InitDoneSec:1, InitDoneCtxTab:1, - InitDoneTimer:1, InitDoneInfo:1; + UINT32 InitDoneFsvrt:1, InitDoneDelRsc:1, InitDoneIoq:1, InitDoneSec:1, InitDoneDir:1, + InitDoneCtxTab:1, InitDoneTimer:1, InitDoneInfo:1; PDEVICE_OBJECT FsctlDeviceObject; PDEVICE_OBJECT FsvrtDeviceObject; HANDLE MupHandle; @@ -672,6 +675,7 @@ typedef struct UNICODE_STRING VolumePrefix; FSP_IOQ *Ioq; FSP_META_CACHE *SecurityCache; + FSP_META_CACHE *DirInfoCache; KSPIN_LOCK ExpirationLock; WORK_QUEUE_ITEM ExpirationWorkItem; BOOLEAN ExpirationInProgress; @@ -781,6 +785,8 @@ typedef struct ERESOURCE PagingIoResource; FAST_MUTEX HeaderFastMutex; SECTION_OBJECT_POINTERS SectionObjectPointers; + KSPIN_LOCK DirInfoSpinLock; + UINT64 DirInfo; /* allows to invalidate DirInfo w/o resources acquired */ } FSP_FILE_NODE_NONPAGED; typedef struct { @@ -808,6 +814,7 @@ typedef struct ULONG InfoChangeNumber; UINT64 Security; ULONG SecurityChangeNumber; + ULONG DirInfoChangeNumber; BOOLEAN TruncateOnClose; union { @@ -827,6 +834,8 @@ typedef struct FSP_FILE_NODE *FileNode; UINT64 UserContext2; BOOLEAN DeleteOnClose; + UNICODE_STRING QueryFileName; + UINT64 QueryOffset; } FSP_FILE_DESC; NTSTATUS FspFileNodeCreate(PDEVICE_OBJECT DeviceObject, ULONG ExtraSize, FSP_FILE_NODE **PFileNode); @@ -872,8 +881,15 @@ BOOLEAN FspFileNodeReferenceSecurity(FSP_FILE_NODE *FileNode, PCVOID *PBuffer, P VOID FspFileNodeSetSecurity(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size); BOOLEAN FspFileNodeTrySetSecurity(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size, ULONG SecurityChangeNumber); +BOOLEAN FspFileNodeReferenceDirInfo(FSP_FILE_NODE *FileNode, PCVOID *PBuffer, PULONG PSize); +VOID FspFileNodeSetDirInfo(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size); +BOOLEAN FspFileNodeTrySetDirInfo(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size, + ULONG DirInfoChangeNumber); +VOID FspFileNodeInvalidateParentDirInfo(FSP_FILE_NODE *FileNode); NTSTATUS FspFileDescCreate(FSP_FILE_DESC **PFileDesc); VOID FspFileDescDelete(FSP_FILE_DESC *FileDesc); +NTSTATUS FspFileDescResetQueryFileName(FSP_FILE_DESC *FileDesc, + PUNICODE_STRING FileName, BOOLEAN Reset); #define FspFileNodeAcquireShared(N,F) FspFileNodeAcquireSharedF(N, FspFileNodeAcquire ## F) #define FspFileNodeTryAcquireShared(N,F) FspFileNodeTryAcquireSharedF(N, FspFileNodeAcquire ## F, FALSE) #define FspFileNodeAcquireExclusive(N,F) FspFileNodeAcquireExclusiveF(N, FspFileNodeAcquire ## F) @@ -883,6 +899,7 @@ VOID FspFileDescDelete(FSP_FILE_DESC *FileDesc); #define FspFileNodeRelease(N,F) FspFileNodeReleaseF(N, FspFileNodeAcquire ## F) #define FspFileNodeReleaseOwner(N,F,P) FspFileNodeReleaseOwnerF(N, FspFileNodeAcquire ## F, P) #define FspFileNodeDereferenceSecurity(P) FspMetaCacheDereferenceItemBuffer(P) +#define FspFileNodeDereferenceDirInfo(P) FspMetaCacheDereferenceItemBuffer(P) /* debug */ #if DBG @@ -919,6 +936,7 @@ extern FAST_IO_DISPATCH FspFastIoDispatch; extern CACHE_MANAGER_CALLBACKS FspCacheManagerCallbacks; extern FSP_IOPREP_DISPATCH *FspIopPrepareFunction[]; extern FSP_IOCMPL_DISPATCH *FspIopCompleteFunction[]; +extern WCHAR FspFileDescQueryFileNameMatchAll[]; /* multiversion support */ typedef diff --git a/src/sys/file.c b/src/sys/file.c index da1bfff6..32f0d9ef 100644 --- a/src/sys/file.c +++ b/src/sys/file.c @@ -38,8 +38,15 @@ BOOLEAN FspFileNodeReferenceSecurity(FSP_FILE_NODE *FileNode, PCVOID *PBuffer, P VOID FspFileNodeSetSecurity(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size); BOOLEAN FspFileNodeTrySetSecurity(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size, ULONG SecurityChangeNumber); +BOOLEAN FspFileNodeReferenceDirInfo(FSP_FILE_NODE *FileNode, PCVOID *PBuffer, PULONG PSize); +VOID FspFileNodeSetDirInfo(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size); +BOOLEAN FspFileNodeTrySetDirInfo(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size, + ULONG DirInfoChangeNumber); +VOID FspFileNodeInvalidateParentDirInfo(FSP_FILE_NODE *FileNode); NTSTATUS FspFileDescCreate(FSP_FILE_DESC **PFileDesc); VOID FspFileDescDelete(FSP_FILE_DESC *FileDesc); +NTSTATUS FspFileDescResetQueryFileName(FSP_FILE_DESC *FileDesc, + PUNICODE_STRING FileName, BOOLEAN Reset); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, FspFileNodeCreate) @@ -66,8 +73,13 @@ VOID FspFileDescDelete(FSP_FILE_DESC *FileDesc); #pragma alloc_text(PAGE, FspFileNodeReferenceSecurity) #pragma alloc_text(PAGE, FspFileNodeSetSecurity) #pragma alloc_text(PAGE, FspFileNodeTrySetSecurity) +// !#pragma alloc_text(PAGE, FspFileNodeReferenceDirInfo) +// !#pragma alloc_text(PAGE, FspFileNodeSetDirInfo) +// !#pragma alloc_text(PAGE, FspFileNodeTrySetDirInfo) +// !#pragma alloc_text(PAGE, FspFileNodeInvalidateParentDirInfo) #pragma alloc_text(PAGE, FspFileDescCreate) #pragma alloc_text(PAGE, FspFileDescDelete) +#pragma alloc_text(PAGE, FspFileDescResetQueryFileName) #endif #define FSP_FILE_NODE_GET_FLAGS() \ @@ -109,6 +121,7 @@ NTSTATUS FspFileNodeCreate(PDEVICE_OBJECT DeviceObject, ExInitializeResourceLite(&NonPaged->Resource); ExInitializeResourceLite(&NonPaged->PagingIoResource); ExInitializeFastMutex(&NonPaged->HeaderFastMutex); + KeInitializeSpinLock(&NonPaged->DirInfoSpinLock); RtlZeroMemory(FileNode, sizeof *FileNode + ExtraSize); FileNode->Header.NodeTypeCode = FspFileNodeFileKind; @@ -139,6 +152,7 @@ VOID FspFileNodeDelete(FSP_FILE_NODE *FileNode) FsRtlTeardownPerStreamContexts(&FileNode->Header); + FspMetaCacheInvalidateItem(FsvolDeviceExtension->DirInfoCache, FileNode->NonPaged->DirInfo); FspMetaCacheInvalidateItem(FsvolDeviceExtension->SecurityCache, FileNode->Security); FspDeviceDereference(FileNode->FsvolDeviceObject); @@ -766,6 +780,88 @@ BOOLEAN FspFileNodeTrySetSecurity(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG return TRUE; } +BOOLEAN FspFileNodeReferenceDirInfo(FSP_FILE_NODE *FileNode, PCVOID *PBuffer, PULONG PSize) +{ + // !PAGED_CODE(); + + FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = + FspFsvolDeviceExtension(FileNode->FsvolDeviceObject); + FSP_FILE_NODE_NONPAGED *NonPaged = FileNode->NonPaged; + UINT64 DirInfo; + + /* no need to acquire the DirInfoSpinLock as the FileNode is acquired */ + DirInfo = NonPaged->DirInfo; + + return FspMetaCacheReferenceItemBuffer(FsvolDeviceExtension->DirInfoCache, + DirInfo, PBuffer, PSize); +} + +VOID FspFileNodeSetDirInfo(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size) +{ + // !PAGED_CODE(); + + FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = + FspFsvolDeviceExtension(FileNode->FsvolDeviceObject); + FSP_FILE_NODE_NONPAGED *NonPaged = FileNode->NonPaged; + KIRQL Irql; + UINT64 DirInfo; + + /* no need to acquire the DirInfoSpinLock as the FileNode is acquired */ + DirInfo = NonPaged->DirInfo; + + FspMetaCacheInvalidateItem(FsvolDeviceExtension->DirInfoCache, DirInfo); + DirInfo = 0 != Buffer ? + FspMetaCacheAddItem(FsvolDeviceExtension->DirInfoCache, Buffer, Size) : 0; + FileNode->DirInfoChangeNumber++; + + /* acquire the DirInfoSpinLock to protect against concurrent FspFileNodeInvalidateParentDirInfo*/ + KeAcquireSpinLock(&NonPaged->DirInfoSpinLock, &Irql); + NonPaged->DirInfo = DirInfo; + KeReleaseSpinLock(&NonPaged->DirInfoSpinLock, Irql); +} + +BOOLEAN FspFileNodeTrySetDirInfo(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size, + ULONG DirInfoChangeNumber) +{ + // !PAGED_CODE(); + + if (FileNode->DirInfoChangeNumber != DirInfoChangeNumber) + return FALSE; + + FspFileNodeSetDirInfo(FileNode, Buffer, Size); + return TRUE; +} + +VOID FspFileNodeInvalidateParentDirInfo(FSP_FILE_NODE *FileNode) +{ + // !PAGED_CODE(); + + PDEVICE_OBJECT FsvolDeviceObject = FileNode->FsvolDeviceObject; + FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = FspFsvolDeviceExtension(FsvolDeviceObject); + UNICODE_STRING Parent, Suffix; + FSP_FILE_NODE_NONPAGED *NonPaged; + KIRQL Irql; + UINT64 DirInfo; + + FspUnicodePathSuffix(&FileNode->FileName, &Parent, &Suffix); + + FspFsvolDeviceLockContextTable(FsvolDeviceObject); + FileNode = FspFsvolDeviceLookupContextByName(FsvolDeviceObject, &Parent); + FspFsvolDeviceUnlockContextTable(FsvolDeviceObject); + + if (0 == FileNode) + return; + + NonPaged = FileNode->NonPaged; + + /* acquire the DirInfoSpinLock to protect against concurrent FspFileNodeSetDirInfo*/ + KeAcquireSpinLock(&NonPaged->DirInfoSpinLock, &Irql); + DirInfo = NonPaged->DirInfo; + KeReleaseSpinLock(&NonPaged->DirInfoSpinLock, Irql); + + FspMetaCacheInvalidateItem(FsvolDeviceExtension->DirInfoCache, DirInfo); +} + NTSTATUS FspFileDescCreate(FSP_FILE_DESC **PFileDesc) { PAGED_CODE(); @@ -783,5 +879,70 @@ VOID FspFileDescDelete(FSP_FILE_DESC *FileDesc) { PAGED_CODE(); + if (0 != FileDesc->QueryFileName.Buffer && + FspFileDescQueryFileNameMatchAll != FileDesc->QueryFileName.Buffer) + { + PDEVICE_OBJECT FsvolDeviceObject = FileDesc->FileNode->FsvolDeviceObject; + FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = + FspFsvolDeviceExtension(FsvolDeviceObject); + + if (FsvolDeviceExtension->VolumeParams.CaseSensitiveSearch) + FspFree(FileDesc->QueryFileName.Buffer); + else + RtlFreeUnicodeString(&FileDesc->QueryFileName); + } + FspFree(FileDesc); } + +NTSTATUS FspFileDescResetQueryFileName(FSP_FILE_DESC *FileDesc, + PUNICODE_STRING FileName, BOOLEAN Reset) +{ + PAGED_CODE(); + + if (Reset || 0 == FileDesc->QueryFileName.Buffer) + { + PDEVICE_OBJECT FsvolDeviceObject = FileDesc->FileNode->FsvolDeviceObject; + FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = + FspFsvolDeviceExtension(FsvolDeviceObject); + UNICODE_STRING QueryFileName; + + if (0 == FileName || (sizeof(WCHAR) == FileName->Length && L'*' == FileName->Buffer[0])) + { + QueryFileName.Length = QueryFileName.MaximumLength = sizeof(WCHAR); /* L"*" */ + QueryFileName.Buffer = FspFileDescQueryFileNameMatchAll; + } + else + { + if (FsvolDeviceExtension->VolumeParams.CaseSensitiveSearch) + { + QueryFileName.Length = QueryFileName.MaximumLength = FileName->Length; + QueryFileName.Buffer = FspAlloc(FileName->Length); + if (0 == QueryFileName.Buffer) + return STATUS_INSUFFICIENT_RESOURCES; + RtlCopyMemory(QueryFileName.Buffer, FileName->Buffer, FileName->Length); + } + else + { + NTSTATUS Result = RtlUpcaseUnicodeString(&QueryFileName, FileName, TRUE); + if (NT_SUCCESS(Result)) + return Result; + } + } + + if (0 != FileDesc->QueryFileName.Buffer && + FspFileDescQueryFileNameMatchAll != FileDesc->QueryFileName.Buffer) + { + if (FsvolDeviceExtension->VolumeParams.CaseSensitiveSearch) + FspFree(FileDesc->QueryFileName.Buffer); + else + RtlFreeUnicodeString(&FileDesc->QueryFileName); + } + + FileDesc->QueryFileName = QueryFileName; + } + + return STATUS_SUCCESS; +} + +WCHAR FspFileDescQueryFileNameMatchAll[] = L"*"; diff --git a/src/sys/util.c b/src/sys/util.c index a9ec4eb5..66ad70c3 100644 --- a/src/sys/util.c +++ b/src/sys/util.c @@ -123,6 +123,9 @@ BOOLEAN FspUnicodePathIsValid(PUNICODE_STRING Path, BOOLEAN AllowStreams) { PAGED_CODE(); + if (0 != Path->Length % sizeof(WCHAR)) + return FALSE; + PWSTR PathBgn, PathEnd, PathPtr; PathBgn = Path->Buffer; diff --git a/src/sys/wq.c b/src/sys/wq.c index 94aab6bf..1e9f37cf 100644 --- a/src/sys/wq.c +++ b/src/sys/wq.c @@ -17,9 +17,9 @@ NTSTATUS FspWqCreateAndPostIrpWorkItem(PIRP Irp, if (0 == RequestWorkItem) { NTSTATUS Result; - - /* probe and lock the user buffer (if not an MDL request) */ PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); + + /* lock/buffer the user buffer */ if ((IRP_MJ_READ == IrpSp->MajorFunction || IRP_MJ_WRITE == IrpSp->MajorFunction) && !FlagOn(IrpSp->MinorFunction, IRP_MN_MDL)) { @@ -30,6 +30,13 @@ NTSTATUS FspWqCreateAndPostIrpWorkItem(PIRP Irp, if (!NT_SUCCESS(Result)) return Result; } + else if (IRP_MJ_DIRECTORY_CONTROL == IrpSp->MajorFunction && + IRP_MN_QUERY_DIRECTORY == IrpSp->MinorFunction) + { + Result = FspBufferUserBuffer(Irp, IrpSp->Parameters.QueryDirectory.Length, IoWriteAccess); + if (!NT_SUCCESS(Result)) + return Result; + } Result = FspIopCreateRequestWorkItem(Irp, sizeof(WORK_QUEUE_ITEM), RequestFini, &RequestWorkItem);