diff --git a/tst/passthrough-dotnet/Program.cs b/tst/passthrough-dotnet/Program.cs index 01f77a06..ae5c7aad 100644 --- a/tst/passthrough-dotnet/Program.cs +++ b/tst/passthrough-dotnet/Program.cs @@ -29,32 +29,212 @@ namespace passthrough { class Ptfs : FileSystem { - protected class FileDesc - { - public FileSystemInfo Info; - public FileStream Stream; - public DirectoryBuffer DirBuffer; + protected const int ALLOCATION_UNIT = 4096; - public FileDesc(FileSystemInfo Info, FileStream Stream) - { - this.Info = Info; - this.Stream = Stream; - } - public UInt32 FileAttributes - { - get - { - return (UInt32)Info.Attributes; - } - set - { - Info.Attributes = 0 == value ? - System.IO.FileAttributes.Normal : (FileAttributes)value; - } - } + protected static void ThrowIoExceptionWithHResult(Int32 HResult) + { + throw new IOException(null, HResult); + } + protected static void ThrowIoExceptionWithWin32(Int32 Error) + { + ThrowIoExceptionWithHResult(unchecked((Int32)(0x80070000 | Error))); + } + protected static void ThrowIoExceptionWithNtStatus(Int32 Status) + { + ThrowIoExceptionWithWin32((Int32)Win32FromNtStatus(Status)); } - private const int ALLOCATION_UNIT = 4096; + protected class FileDesc + { + public FileStream Stream; + public DirectoryInfo DirInfo; + public DirectoryBuffer DirBuffer; + + public FileDesc(FileStream Stream) + { + this.Stream = Stream; + } + public FileDesc(DirectoryInfo DirInfo) + { + this.DirInfo = DirInfo; + } + public static void GetFileInfoFromFileSystemInfo( + FileSystemInfo Info, + out FileInfo FileInfo) + { + FileInfo.FileAttributes = (UInt32)Info.Attributes; + FileInfo.ReparseTag = 0; + FileInfo.FileSize = Info is System.IO.FileInfo ? + (UInt64)((System.IO.FileInfo)Info).Length : 0; + FileInfo.AllocationSize = (FileInfo.FileSize + ALLOCATION_UNIT - 1) + / ALLOCATION_UNIT * ALLOCATION_UNIT; + FileInfo.CreationTime = (UInt64)Info.CreationTimeUtc.ToFileTimeUtc(); + FileInfo.LastAccessTime = (UInt64)Info.LastAccessTimeUtc.ToFileTimeUtc(); + FileInfo.LastWriteTime = (UInt64)Info.LastWriteTimeUtc.ToFileTimeUtc(); + FileInfo.ChangeTime = FileInfo.LastWriteTime; + FileInfo.IndexNumber = 0; + FileInfo.HardLinks = 0; + } + public Int32 GetFileInfo(out FileInfo FileInfo) + { + if (null != Stream) + { + BY_HANDLE_FILE_INFORMATION Info; + if (!GetFileInformationByHandle(Stream.SafeFileHandle.DangerousGetHandle(), + out Info)) + ThrowIoExceptionWithWin32(Marshal.GetLastWin32Error()); + FileInfo.FileAttributes = Info.dwFileAttributes; + FileInfo.ReparseTag = 0; + FileInfo.FileSize = (UInt64)Stream.Length; + FileInfo.AllocationSize = (FileInfo.FileSize + ALLOCATION_UNIT - 1) + / ALLOCATION_UNIT * ALLOCATION_UNIT; + FileInfo.CreationTime = Info.ftCreationTime; + FileInfo.LastAccessTime = Info.ftLastAccessTime; + FileInfo.LastWriteTime = Info.ftLastWriteTime; + FileInfo.ChangeTime = FileInfo.LastWriteTime; + FileInfo.IndexNumber = 0; + FileInfo.HardLinks = 0; + } + else + GetFileInfoFromFileSystemInfo(DirInfo, out FileInfo); + return STATUS_SUCCESS; + } + public void SetBasicInfo( + UInt32 FileAttributes, + UInt64 CreationTime, + UInt64 LastAccessTime, + UInt64 LastWriteTime) + { + if (0 == FileAttributes) + FileAttributes = (UInt32)System.IO.FileAttributes.Normal; + if (null != Stream) + { + FILE_BASIC_INFO Info = default(FILE_BASIC_INFO); + if (unchecked((UInt32)(-1)) != FileAttributes) + Info.FileAttributes = FileAttributes; + if (0 != CreationTime) + Info.CreationTime = CreationTime; + if (0 != LastAccessTime) + Info.LastAccessTime = LastAccessTime; + if (0 != LastWriteTime) + Info.LastWriteTime = LastWriteTime; + if (!SetFileInformationByHandle(Stream.SafeFileHandle.DangerousGetHandle(), + 0/*FileBasicInfo*/, ref Info, (UInt32)Marshal.SizeOf(Info))) + ThrowIoExceptionWithWin32(Marshal.GetLastWin32Error()); + } + else + { + if (unchecked((UInt32)(-1)) != FileAttributes) + DirInfo.Attributes = (System.IO.FileAttributes)FileAttributes; + if (0 != CreationTime) + DirInfo.CreationTimeUtc = DateTime.FromFileTimeUtc((Int64)CreationTime); + if (0 != LastAccessTime) + DirInfo.LastAccessTimeUtc = DateTime.FromFileTimeUtc((Int64)LastAccessTime); + if (0 != LastWriteTime) + DirInfo.LastWriteTimeUtc = DateTime.FromFileTimeUtc((Int64)LastWriteTime); + } + } + public UInt32 GetFileAttributes() + { + FileInfo FileInfo; + GetFileInfo(out FileInfo); + return FileInfo.FileAttributes; + } + public void SetFileAttributes(UInt32 FileAttributes) + { + SetBasicInfo(FileAttributes, 0, 0, 0); + } + public Byte[] GetSecurityDescriptor() + { + if (null != Stream) + return Stream.GetAccessControl().GetSecurityDescriptorBinaryForm(); + else + return DirInfo.GetAccessControl().GetSecurityDescriptorBinaryForm(); + } + public void SetSecurityDescriptor(AccessControlSections Sections, Byte[] SecurityDescriptor) + { + if (null != Stream) + { + FileSecurity Security = Stream.GetAccessControl(); + Security.SetSecurityDescriptorBinaryForm(SecurityDescriptor, Sections); + Stream.SetAccessControl(Security); + } + else + { + DirectorySecurity Security = DirInfo.GetAccessControl(); + Security.SetSecurityDescriptorBinaryForm(SecurityDescriptor, Sections); + DirInfo.SetAccessControl(Security); + } + } + public void SetDisposition(Boolean Safe) + { + if (null != Stream) + { + FILE_DISPOSITION_INFO Info; + Info.DeleteFile = true; + if (!SetFileInformationByHandle(Stream.SafeFileHandle.DangerousGetHandle(), + 4/*FileDispositionInfo*/, ref Info, (UInt32)Marshal.SizeOf(Info))) + if (!Safe) + ThrowIoExceptionWithWin32(Marshal.GetLastWin32Error()); + } + else + try + { + DirInfo.Delete(); + } + catch (Exception ex) + { + if (!Safe) + ThrowIoExceptionWithHResult(ex.HResult); + } + } + + /* interop */ + [StructLayout(LayoutKind.Sequential, Pack = 4)] + private struct BY_HANDLE_FILE_INFORMATION + { + public UInt32 dwFileAttributes; + public UInt64 ftCreationTime; + public UInt64 ftLastAccessTime; + public UInt64 ftLastWriteTime; + public UInt32 dwVolumeSerialNumber; + public UInt32 nFileSizeHigh; + public UInt32 nFileSizeLow; + public UInt32 nNumberOfLinks; + public UInt32 nFileIndexHigh; + public UInt32 nFileIndexLow; + } + [StructLayout(LayoutKind.Sequential)] + private struct FILE_BASIC_INFO + { + public UInt64 CreationTime; + public UInt64 LastAccessTime; + public UInt64 LastWriteTime; + public UInt64 ChangeTime; + public UInt32 FileAttributes; + } + [StructLayout(LayoutKind.Sequential)] + private struct FILE_DISPOSITION_INFO + { + public Boolean DeleteFile; + } + [DllImport("kernel32.dll", SetLastError = true)] + private static extern Boolean GetFileInformationByHandle( + IntPtr hFile, + out BY_HANDLE_FILE_INFORMATION lpFileInformation); + [DllImport("kernel32.dll", SetLastError = true)] + private static extern Boolean SetFileInformationByHandle( + IntPtr hFile, + Int32 FileInformationClass, + ref FILE_BASIC_INFO lpFileInformation, + UInt32 dwBufferSize); + [DllImport("kernel32.dll", SetLastError = true)] + private static extern Boolean SetFileInformationByHandle( + IntPtr hFile, + Int32 FileInformationClass, + ref FILE_DISPOSITION_INFO lpFileInformation, + UInt32 dwBufferSize); + } public Ptfs() : base() { @@ -85,37 +265,10 @@ namespace passthrough return NtStatusFromWin32((UInt32)HResult & 0xFFFF); return STATUS_UNEXPECTED_IO_ERROR; } - private Int32 HResultFromWin32(UInt32 Error) - { - return unchecked((Int32)(0x80070000 | Error)); - } - private void ThrowIoException(Int32 Result) - { - throw new IOException(null, HResultFromWin32(Win32FromNtStatus(Result))); - } protected String ConcatPath(String FileName) { return _Path + FileName; } - protected Int32 GetFileInfoInternal(FileSystemInfo Info, Boolean Refresh, - out FileInfo FileInfo) - { - if (Refresh) - Info.Refresh(); - FileInfo.FileAttributes = (UInt32)Info.Attributes; - FileInfo.ReparseTag = 0; - FileInfo.FileSize = Info is System.IO.FileInfo ? - (UInt64)((System.IO.FileInfo)Info).Length : 0; - FileInfo.AllocationSize = (FileInfo.FileSize + ALLOCATION_UNIT - 1) - / ALLOCATION_UNIT * ALLOCATION_UNIT; - FileInfo.CreationTime = (UInt64)Info.CreationTimeUtc.ToFileTimeUtc(); - FileInfo.LastAccessTime = (UInt64)Info.LastAccessTimeUtc.ToFileTimeUtc(); - FileInfo.LastWriteTime = (UInt64)Info.LastWriteTimeUtc.ToFileTimeUtc(); - FileInfo.ChangeTime = FileInfo.LastWriteTime; - FileInfo.IndexNumber = 0; - FileInfo.HardLinks = 0; - return STATUS_SUCCESS; - } protected override Int32 GetVolumeInfo( out VolumeInfo VolumeInfo) { @@ -159,46 +312,53 @@ namespace passthrough out FileInfo FileInfo, out String NormalizedName) { - FileDesc FileDesc; - FileName = ConcatPath(FileName); - if (0 != (CreateOptions & FILE_DIRECTORY_FILE)) + FileDesc FileDesc = null; + try { - if (Directory.Exists(FileName)) - ThrowIoException(STATUS_OBJECT_NAME_COLLISION); - DirectorySecurity Security = null; - if (null != SecurityDescriptor) + FileName = ConcatPath(FileName); + if (0 == (CreateOptions & FILE_DIRECTORY_FILE)) { - Security = new DirectorySecurity(); - Security.SetSecurityDescriptorBinaryForm(SecurityDescriptor); + FileSecurity Security = null; + if (null != SecurityDescriptor) + { + Security = new FileSecurity(); + Security.SetSecurityDescriptorBinaryForm(SecurityDescriptor); + } + FileDesc = new FileDesc( + new FileStream( + FileName, + FileMode.CreateNew, + (FileSystemRights)GrantedAccess | FileSystemRights.WriteAttributes, + FileShare.Read | FileShare.Write | FileShare.Delete, + 4096, + 0, + Security)); } - FileDesc = new FileDesc( - Directory.CreateDirectory(FileName, Security), - null); + else + { + if (Directory.Exists(FileName)) + ThrowIoExceptionWithNtStatus(STATUS_OBJECT_NAME_COLLISION); + DirectorySecurity Security = null; + if (null != SecurityDescriptor) + { + Security = new DirectorySecurity(); + Security.SetSecurityDescriptorBinaryForm(SecurityDescriptor); + } + FileDesc = new FileDesc( + Directory.CreateDirectory(FileName, Security)); + } + FileDesc.SetFileAttributes(FileAttributes); + FileNode = default(Object); + FileDesc0 = FileDesc; + NormalizedName = default(String); + return FileDesc.GetFileInfo(out FileInfo); } - else + catch { - FileSecurity Security = null; - if (null != SecurityDescriptor) - { - Security = new FileSecurity(); - Security.SetSecurityDescriptorBinaryForm(SecurityDescriptor); - } - FileDesc = new FileDesc( - new System.IO.FileInfo(FileName), - new FileStream( - FileName, - FileMode.CreateNew, - (FileSystemRights)GrantedAccess, - FileShare.Read | FileShare.Write | FileShare.Delete, - 4096, - 0, - Security)); + if (null != FileDesc && null != FileDesc.Stream) + FileDesc.Stream.Dispose(); + throw; } - FileDesc.FileAttributes = FileAttributes; - FileNode = default(Object); - FileDesc0 = FileDesc; - NormalizedName = default(String); - return GetFileInfoInternal(FileDesc.Info, false, out FileInfo); } protected override Int32 Open( String FileName, @@ -209,30 +369,37 @@ namespace passthrough out FileInfo FileInfo, out String NormalizedName) { - FileDesc FileDesc; - FileName = ConcatPath(FileName); - if (Directory.Exists(FileName)) + FileDesc FileDesc = null; + try { - FileDesc = new FileDesc( - new System.IO.DirectoryInfo(FileName), - null); + FileName = ConcatPath(FileName); + if (!Directory.Exists(FileName)) + { + FileDesc = new FileDesc( + new FileStream( + FileName, + FileMode.Open, + (FileSystemRights)GrantedAccess, + FileShare.Read | FileShare.Write | FileShare.Delete, + 4096, + 0)); + } + else + { + FileDesc = new FileDesc( + new DirectoryInfo(FileName)); + } + FileNode = default(Object); + FileDesc0 = FileDesc; + NormalizedName = default(String); + return FileDesc.GetFileInfo(out FileInfo); } - else + catch { - FileDesc = new FileDesc( - new System.IO.FileInfo(FileName), - new FileStream( - FileName, - FileMode.Open, - (FileSystemRights)GrantedAccess, - FileShare.Read | FileShare.Write | FileShare.Delete, - 4096, - 0)); + if (null != FileDesc && null != FileDesc.Stream) + FileDesc.Stream.Dispose(); + throw; } - FileNode = default(Object); - FileDesc0 = FileDesc; - NormalizedName = default(String); - return GetFileInfoInternal(FileDesc.Info, false, out FileInfo); } protected override Int32 Overwrite( Object FileNode, @@ -244,11 +411,11 @@ namespace passthrough { FileDesc FileDesc = (FileDesc)FileDesc0; if (ReplaceFileAttributes) - FileDesc.FileAttributes = FileAttributes; + FileDesc.SetFileAttributes(FileAttributes); else if (0 != FileAttributes) - FileDesc.FileAttributes |= FileAttributes; + FileDesc.SetFileAttributes(FileDesc.GetFileAttributes() | FileAttributes); FileDesc.Stream.SetLength(0); - return GetFileInfoInternal(FileDesc.Info, true, out FileInfo); + return FileDesc.GetFileInfo(out FileInfo); } protected override void Cleanup( Object FileNode, @@ -259,13 +426,7 @@ namespace passthrough FileDesc FileDesc = (FileDesc)FileDesc0; if (0 != (Flags & CleanupDelete)) { - try - { - FileDesc.Info.Delete(); - } - catch - { - } + FileDesc.SetDisposition(true); if (null != FileDesc.Stream) FileDesc.Stream.Dispose(); } @@ -289,6 +450,8 @@ namespace passthrough out UInt32 PBytesTransferred) { FileDesc FileDesc = (FileDesc)FileDesc0; + if (Offset >= (UInt64)FileDesc.Stream.Length) + ThrowIoExceptionWithNtStatus(STATUS_END_OF_FILE); Byte[] Bytes = new byte[Length]; FileDesc.Stream.Seek((Int64)Offset, SeekOrigin.Begin); PBytesTransferred = (UInt32)FileDesc.Stream.Read(Bytes, 0, Bytes.Length); @@ -307,26 +470,24 @@ namespace passthrough out FileInfo FileInfo) { FileDesc FileDesc = (FileDesc)FileDesc0; - UInt64 FileSize; if (ConstrainedIo) { - FileDesc.Info.Refresh(); - FileSize = (UInt64)((System.IO.FileInfo)FileDesc.Info).Length; - if (Offset >= FileSize) + if (Offset >= (UInt64)FileDesc.Stream.Length) { PBytesTransferred = default(UInt32); FileInfo = default(FileInfo); return STATUS_SUCCESS; } - if (Offset + Length > FileSize) - Length = (UInt32)(FileSize - Offset); + if (Offset + Length > (UInt64)FileDesc.Stream.Length) + Length = (UInt32)((UInt64)FileDesc.Stream.Length - Offset); } Byte[] Bytes = new byte[Length]; Marshal.Copy(Buffer, Bytes, 0, Bytes.Length); - FileDesc.Stream.Seek((Int64)Offset, SeekOrigin.Begin); + if (!WriteToEndOfFile) + FileDesc.Stream.Seek((Int64)Offset, SeekOrigin.Begin); FileDesc.Stream.Write(Bytes, 0, Bytes.Length); PBytesTransferred = (UInt32)Bytes.Length; - return GetFileInfoInternal(FileDesc.Info, true, out FileInfo); + return FileDesc.GetFileInfo(out FileInfo); } protected override Int32 Flush( Object FileNode, @@ -341,7 +502,7 @@ namespace passthrough return STATUS_SUCCESS; } FileDesc.Stream.Flush(true); - return GetFileInfoInternal(FileDesc.Info, true, out FileInfo); + return FileDesc.GetFileInfo(out FileInfo); } protected override Int32 GetFileInfo( Object FileNode, @@ -349,7 +510,7 @@ namespace passthrough out FileInfo FileInfo) { FileDesc FileDesc = (FileDesc)FileDesc0; - return GetFileInfoInternal(FileDesc.Info, true, out FileInfo); + return FileDesc.GetFileInfo(out FileInfo); } protected override Int32 SetBasicInfo( Object FileNode, @@ -362,15 +523,8 @@ namespace passthrough out FileInfo FileInfo) { FileDesc FileDesc = (FileDesc)FileDesc0; - if (unchecked((UInt32)(-1)) != FileAttributes) - FileDesc.FileAttributes = FileAttributes; - if (0 != CreationTime) - FileDesc.Info.CreationTimeUtc = DateTime.FromFileTimeUtc((Int64)CreationTime); - if (0 != LastAccessTime) - FileDesc.Info.LastAccessTimeUtc = DateTime.FromFileTimeUtc((Int64)LastAccessTime); - if (0 != LastWriteTime) - FileDesc.Info.LastWriteTimeUtc = DateTime.FromFileTimeUtc((Int64)LastWriteTime); - return GetFileInfoInternal(FileDesc.Info, true, out FileInfo); + FileDesc.SetBasicInfo(FileAttributes, CreationTime, LastAccessTime, LastWriteTime); + return FileDesc.GetFileInfo(out FileInfo); } protected override Int32 SetFileSize( Object FileNode, @@ -380,8 +534,7 @@ namespace passthrough out FileInfo FileInfo) { FileDesc FileDesc = (FileDesc)FileDesc0; - GetFileInfoInternal(FileDesc.Info, true, out FileInfo); - if (!SetAllocationSize || FileInfo.FileSize > NewSize) + if (!SetAllocationSize || (UInt64)FileDesc.Stream.Length > NewSize) { /* * "FileInfo.FileSize > NewSize" explanation: @@ -389,11 +542,8 @@ namespace passthrough * is less than the current FileSize we must truncate the file. */ FileDesc.Stream.SetLength((Int64)NewSize); - FileInfo.FileSize = NewSize; - FileInfo.AllocationSize = (FileInfo.FileSize + ALLOCATION_UNIT - 1) - / ALLOCATION_UNIT * ALLOCATION_UNIT; } - return STATUS_SUCCESS; + return FileDesc.GetFileInfo(out FileInfo); } protected override Int32 CanDelete( Object FileNode, @@ -401,11 +551,7 @@ namespace passthrough String FileName) { FileDesc FileDesc = (FileDesc)FileDesc0; - /* - * If a file has an open handle the Delete call below - * will only mark it for disposition. - */ - FileDesc.Info.Delete(); + FileDesc.SetDisposition(false); return STATUS_SUCCESS; } protected override Int32 Rename( @@ -418,18 +564,18 @@ namespace passthrough FileDesc FileDesc = (FileDesc)FileDesc0; FileName = ConcatPath(FileName); NewFileName = ConcatPath(NewFileName); - if (null == FileDesc.Stream) - { - if (ReplaceIfExists) - throw new UnauthorizedAccessException(); - Directory.Move(FileName, NewFileName); - } - else + if (null != FileDesc.Stream) { if (ReplaceIfExists) File.Delete(NewFileName); File.Move(FileName, NewFileName); } + else + { + if (ReplaceIfExists) + throw new UnauthorizedAccessException(); + Directory.Move(FileName, NewFileName); + } return STATUS_SUCCESS; } protected override Int32 GetSecurity( @@ -438,12 +584,7 @@ namespace passthrough ref Byte[] SecurityDescriptor) { FileDesc FileDesc = (FileDesc)FileDesc0; - if (null == FileDesc.Stream) - SecurityDescriptor = ((System.IO.DirectoryInfo)FileDesc.Info).GetAccessControl(). - GetSecurityDescriptorBinaryForm(); - else - SecurityDescriptor = ((System.IO.FileInfo)FileDesc.Info).GetAccessControl(). - GetSecurityDescriptorBinaryForm(); + SecurityDescriptor = FileDesc.GetSecurityDescriptor(); return STATUS_SUCCESS; } protected override Int32 SetSecurity( @@ -453,18 +594,7 @@ namespace passthrough Byte[] SecurityDescriptor) { FileDesc FileDesc = (FileDesc)FileDesc0; - if (null == FileDesc.Stream) - { - DirectorySecurity Security = ((System.IO.DirectoryInfo)FileDesc.Info).GetAccessControl(); - Security.SetSecurityDescriptorBinaryForm(SecurityDescriptor, Sections); - ((System.IO.DirectoryInfo)FileDesc.Info).SetAccessControl(Security); - } - else - { - FileSecurity Security = ((System.IO.FileInfo)FileDesc.Info).GetAccessControl(); - Security.SetSecurityDescriptorBinaryForm(SecurityDescriptor, Sections); - ((System.IO.FileInfo)FileDesc.Info).SetAccessControl(Security); - } + FileDesc.SetSecurityDescriptor(Sections, SecurityDescriptor); return STATUS_SUCCESS; } protected override Int32 ReadDirectory( @@ -496,14 +626,13 @@ namespace passthrough FileDesc FileDesc = (FileDesc)FileDesc0; if (null == Pattern) Pattern = "*"; - Context = (FileDesc.Info as DirectoryInfo).EnumerateFileSystemInfos(Pattern). - GetEnumerator(); + Context = FileDesc.DirInfo.EnumerateFileSystemInfos(Pattern).GetEnumerator(); } IEnumerator InfoEnumerator = (IEnumerator)Context; if (InfoEnumerator.MoveNext()) { FileName = InfoEnumerator.Current.Name; - GetFileInfoInternal(InfoEnumerator.Current, false, out FileInfo); + FileDesc.GetFileInfoFromFileSystemInfo(InfoEnumerator.Current, out FileInfo); return true; } else