/// <inheritdoc/> public override bool CloseFileHandle(string path, LVFSContextInfo info) { // This is the same as Cleanup as there's only one thing that might need tidying, but according to a comment in the DokanNet Mirror example, there potentially are situations where only one of the two is called. try { object context; if (info.Context.TryGetValue(this, out context)) { (context as FileStream)?.Dispose(); info.Context.Remove(this); } if (info.DeleteOnClose) { if (info.IsDirectory && Directory.Exists(ConvertPath(path))) { Directory.Delete(ConvertPath(path)); } else if (File.Exists(ConvertPath(path))) { File.Delete(ConvertPath(path)); } } return(PredecessorCloseFileHandle(path, info)); } catch (Exception) { // Because there're no checked exceptions in C#, I can't tell what might go wrong here and catch specific exceptions. PredecessorCloseFileHandle(path, info); return(false); } }
/// <inheritdoc/> public override NtStatus FlushBuffers(string path, LVFSContextInfo info) { NtStatus result = DokanResult.Success; if (info.Context.ContainsKey(this)) { try { FileStream stream = info.Context[this] as FileStream; if (stream != null) { stream.Flush(); } } catch (IOException) { result = DokanResult.DiskFull; } } NtStatus predecessorResult = PredecessorFlushBuffers(path, info); if (result == DokanResult.Success && predecessorResult != DokanResult.Success) { return(predecessorResult); } else { return(result); } }
/// <inheritdoc/> public override bool CleanupFileHandle(string path, LVFSContextInfo info) { try { object context; if (info.Context.TryGetValue(this, out context)) { (context as FileStream)?.Dispose(); info.Context.Remove(this); } if (info.DeleteOnClose) { if (info.IsDirectory && Directory.Exists(ConvertPath(path))) { Directory.Delete(ConvertPath(path)); } else if (File.Exists(ConvertPath(path))) { File.Delete(ConvertPath(path)); } } return(PredecessorCleanupFileHandle(path, info)); } catch (Exception) { // Because there're no checked exceptions in C#, I can't tell what might go wrong here and catch specific exceptions. PredecessorCleanupFileHandle(path, info); return(false); } }
/// <inheritdoc/> public override NtStatus SetLength(string path, long length, LVFSContextInfo info) { if (ControlsFile(path)) { try { var stream = info.Context[this] as FileStream; if (stream != null) { stream.SetLength(length); } else { new FileStream(ConvertPath(path), FileMode.Open).SetLength(length); } return(DokanResult.Success); } catch (IOException) { return(DokanResult.DiskFull); } catch (UnauthorizedAccessException) { return(DokanResult.AccessDenied); } } else { return(PredecessorSetLength(path, length, info)); } }
/// <summary> /// Moves the file/directory from its current path to a new one, replacing any existing files only if replace is set. /// </summary> /// <param name="currentPath">The current path of the file/directory</param> /// <param name="newPath">The new path of the file/directory</param> /// <param name="replace">Whether to replace any existing file occupying the new path</param> /// <param name="info">Information concerning the context for this operation.</param> /// <returns><see cref="DokanResult.Success"/> if the file was moved. Otherwise, an appropriate error status.</returns> public NtStatus MoveFile(string currentPath, string newPath, bool replace, LVFSContextInfo info) { WritableSource writable = Last as WritableSource; if (writable != null) { return(writable.MoveFile(currentPath, newPath, replace, info)); } else { return(DokanResult.AccessDenied); } }
/// <summary> /// Clears any buffers for the context, and ensures any buffered data is written to the actual file. /// </summary> /// <param name="path">The path to the file whose buffers to flush</param> /// <param name="info">Information concerning the context for the operation</param> /// <returns><see cref="DokanResult.Success"/> if all buffers were flushed, If not, an appropriate error status.</returns> public NtStatus FlushBuffers(string path, LVFSContextInfo info) { WritableSource writable = Last as WritableSource; if (writable != null) { return(writable.FlushBuffers(path, info)); } else { return(DokanResult.NotImplemented); } }
/// <summary> /// Sets the length of the file. /// </summary> /// <param name="path">The path to the file</param> /// <param name="length">The new length of the file</param> /// <param name="info">Information concerning the context of this operation</param> /// <returns><see cref="DokanResult.Success"/> if the requested length is now the length of the file. If not, an appropriate error status.</returns> public NtStatus SetLength(string path, long length, LVFSContextInfo info) { WritableSource writable = Last as WritableSource; if (writable != null) { return(writable.SetLength(path, length, info)); } else { return(DokanResult.AccessDenied); } }
/// <summary> /// Sets the allocated size for the file. If this is less than the current length, trucate the file. If the file does not grow to fill this space before the handle is released, it may be freed. /// </summary> /// <param name="path">The path to the file</param> /// <param name="allocationSize">The new size to allocate</param> /// <param name="info">Information concerning the context for this operation</param> /// <returns><see cref="DokanResult.Success"/> if the allocation size was changed or already the correct value. If not, an appropriate error status.</returns> public NtStatus SetAllocatedSize(string path, long allocationSize, LVFSContextInfo info) { WritableSource writable = Last as WritableSource; if (writable != null) { return(writable.SetAllocatedSize(path, allocationSize, info)); } else { return(DokanResult.AccessDenied); } }
/// <inheritdoc/> public override NtStatus SetAllocatedSize(string path, long allocationSize, LVFSContextInfo info) { // C# offers no ability to allocate space for a file without actually setting its length, so this function cannot be implemented without calling native code. In the mirror example, this is implemented with incorrect semantics. Despite that, many operations require this to claim to work, so we return success whenever this is called. if (ControlsFile(path)) { if (allocationSize < (info.Context[this] as FileStream).Length) { (info.Context[this] as FileStream).SetLength(allocationSize); } return(DokanResult.Success); } else { return(PredecessorSetAllocatedSize(path, allocationSize, info)); } }
/// <inheritdoc/> public override bool CleanupFileHandle(string path, LVFSContextInfo info) { try { object context; if (info.Context.TryGetValue(this, out context)) { (context as FileStream)?.Dispose(); info.Context.Remove(this); } return(true); } catch (Exception) { // Because there're no checked exceptions in C#, I can't tell what might go wrong here and catch specific exceptions. return(false); } }
/// <inheritdoc/> public override bool CloseFileHandle(string path, LVFSContextInfo info) { // This is the same as Cleanup as there's only one thing that might need tidying, but according to a comment in the DokanNet Mirror example, there potentially are situations where only one of the two is called. try { object context; if (info.Context.TryGetValue(this, out context)) { (context as FileStream)?.Dispose(); info.Context.Remove(this); } return(true); } catch (Exception) { // Because there're no checked exceptions in C#, I can't tell what might go wrong here and catch specific exceptions. return(false); } }
/// <summary> /// Sets the security attributes for the specified sections of the specified file or directory. /// </summary> /// <param name="path">The path to the file</param> /// <param name="security">The security to set</param> /// <param name="sections">The access control sections to change</param> /// <param name="info">Information concerning the context of this operation.</param> /// <returns><see cref="DokanResult.Success"/> if the operation was successful. If not, an appropriate error status.</returns> public NtStatus SetFileSecurity(string path, FileSystemSecurity security, AccessControlSections sections, LVFSContextInfo info) { WritableSource writable = Last as WritableSource; if (writable != null) { return(writable.SetFileSecurity(path, security, sections, info)); } else { return(DokanResult.AccessDenied); } }
/// <inheritdoc/> public override NtStatus CreateFileHandle(string path, DokanNet.FileAccess access, FileShare share, FileMode mode, FileOptions options, FileAttributes attributes, LVFSContextInfo info) { var filePath = ConvertPath(path); var isDirectory = Directory.Exists(filePath); var pathExists = isDirectory || File.Exists(filePath); // Check this first for performance reasons. Keep checking it later in case anything's been meddled with if (!pathExists) { return(PredecessorCreateFileHandle(path, access, share, mode, options, attributes, info)); } if (info.IsDirectory) { try { switch (mode) { case FileMode.Open: if (Directory.Exists(filePath)) { // The original Dokan mirror this is based on called new DirectoryInfo(filePath).EnumerateFileSystemInfos().Any();, but did nothing with its result at this point return(DokanResult.Success); } else if (File.Exists(filePath)) { return(NtStatus.NotADirectory); } else { return(PredecessorCreateFileHandle(path, access, share, mode, options, attributes, info)); } case FileMode.OpenOrCreate: if (Directory.Exists(filePath)) { return(DokanResult.Success); } else if (File.Exists(filePath)) { return(DokanResult.FileExists); } else { return(PredecessorCreateFileHandle(path, access, share, mode, options, attributes, info)); } case FileMode.CreateNew: if (Directory.Exists(filePath)) { return(DokanResult.AlreadyExists); } else if (File.Exists(filePath)) { return(DokanResult.FileExists); } else { return(PredecessorCreateFileHandle(path, access, share, mode, options, attributes, info)); } default: if (Directory.Exists(filePath) || File.Exists(filePath)) { return(DokanResult.AccessDenied); } else { return(PredecessorCreateFileHandle(path, access, share, mode, options, attributes, info)); } } } catch (UnauthorizedAccessException) { return(DokanResult.AccessDenied); } } else { switch (mode) { case FileMode.Open: if (pathExists) { var dataAccess = DokanNet.FileAccess.ReadData | DokanNet.FileAccess.WriteData | DokanNet.FileAccess.AppendData | DokanNet.FileAccess.Execute | DokanNet.FileAccess.GenericExecute | DokanNet.FileAccess.GenericWrite | DokanNet.FileAccess.GenericRead; var readWriteOnlyAttributes = (access & dataAccess) == 0; if (isDirectory || readWriteOnlyAttributes) { if (isDirectory && access.HasFlag(DokanNet.FileAccess.Delete) && !access.HasFlag(DokanNet.FileAccess.Synchronize)) { // It's a delete request on a directory. return(DokanResult.AccessDenied); } info.IsDirectory = isDirectory; return(DokanResult.Success); } else { // Go to the regular handler break; } } else { return(PredecessorCreateFileHandle(path, access, share, mode, options, attributes, info)); } case FileMode.OpenOrCreate: if (pathExists) { // Go to the regular handler break; } else { return(PredecessorCreateFileHandle(path, access, share, mode, options, attributes, info)); } case FileMode.CreateNew: if (pathExists) { return(DokanResult.FileExists); } else { return(PredecessorCreateFileHandle(path, access, share, mode, options, attributes, info)); } case FileMode.Truncate: if (pathExists) { return(DokanResult.AccessDenied); } else { return(PredecessorCreateFileHandle(path, access, share, mode, options, attributes, info)); } case FileMode.Create: if (pathExists) { return(DokanResult.AccessDenied); } else { return(PredecessorCreateFileHandle(path, access, share, mode, options, attributes, info)); } case FileMode.Append: if (pathExists) { return(DokanResult.AccessDenied); } else { return(PredecessorCreateFileHandle(path, access, share, mode, options, attributes, info)); } default: // This code should never be reached throw new ArgumentException("Unknown file mode: " + mode, nameof(mode)); } try { info.Context[this] = new FileStream(filePath, mode, System.IO.FileAccess.Read, share, 4096, options); if (mode == FileMode.Open) { return(DokanResult.Success); } else { return(DokanResult.AlreadyExists); } } catch (UnauthorizedAccessException) { return(DokanResult.AccessDenied); } catch (DirectoryNotFoundException) { return(DokanResult.PathNotFound); } catch (Exception ex) { var hr = (uint)System.Runtime.InteropServices.Marshal.GetHRForException(ex); switch (hr) { case 0x80070020: //Sharing violation return(DokanResult.SharingViolation); default: throw; } } } }
/// <inheritdoc/> public override NtStatus SetFileSecurity(string path, FileSystemSecurity security, AccessControlSections sections, LVFSContextInfo info) { if (ControlsFile(path)) { FileSystemSecurity actualSecurity; if (info.IsDirectory) { actualSecurity = Directory.GetAccessControl(ConvertPath(path), sections); } else { actualSecurity = File.GetAccessControl(ConvertPath(path), sections); } var desiredSddlForm = security.GetSecurityDescriptorSddlForm(sections); actualSecurity.SetSecurityDescriptorSddlForm(desiredSddlForm, sections); if (info.IsDirectory) { Directory.SetAccessControl(ConvertPath(path), actualSecurity as DirectorySecurity); } else { File.SetAccessControl(ConvertPath(path), actualSecurity as FileSecurity); } return(DokanResult.Success); } else { return(PredecessorSetFileSecurity(path, security, sections, info)); } }
/// <summary> /// Called when a file handle is requested /// </summary> /// <param name="path">The path to the file</param> /// <param name="access">The type of access required</param> /// <param name="share">The kind of access other filestreams can have</param> /// <param name="mode">The mode to open the file in</param> /// <param name="options">Advanced options for creating a FileStream</param> /// <param name="attributes">The attributes of the file</param> /// <param name="info">An LVFSinfo containing the context for the file handle and information on the file</param> /// <returns>An NtStatus explaining the success level of the operation. If mode is OpenOrCreate and Create, and the operation is successful opening an existing file, DokanResult.AlreadyExists is returned.</returns> public NtStatus CreateFileHandle(string path, DokanNet.FileAccess access, System.IO.FileShare share, System.IO.FileMode mode, System.IO.FileOptions options, System.IO.FileAttributes attributes, LVFSContextInfo info) { return(Last.CreateFileHandle(path, access, share, mode, options, attributes, info)); }
/// <inheritdoc/> public override bool ReadFile(string path, byte[] buffer, out int bytesRead, long offset, LVFSContextInfo info) { if (info != null && info.Context.ContainsKey(this)) { var stream = info.Context[this] as FileStream; lock (stream) { stream.Position = offset; bytesRead = stream.Read(buffer, 0, buffer.Length); } return(true); } else if (File.Exists(ConvertPath(path))) { using (var stream = new FileStream(ConvertPath(path), FileMode.Open, System.IO.FileAccess.Read)) { stream.Position = offset; bytesRead = stream.Read(buffer, 0, buffer.Length); } return(true); } else { return(PredecessorReadFile(path, buffer, out bytesRead, offset, info)); } }
/// <inheritdoc/> public override NtStatus MoveFile(string currentPath, string newPath, bool replace, LVFSContextInfo info) { var fullCurrentPath = ConvertPath(currentPath); var fullNewPath = ConvertPath(newPath); if (ControlsFile(currentPath)) { try { object context; info.Context.TryGetValue(this, out context); (context as FileStream)?.Dispose(); info.Context[this] = null; // Maybe reopen file stream once file is moved. if (!HasFile(newPath)) { if (info.IsDirectory) { Directory.Move(fullCurrentPath, fullNewPath); } else { File.Move(fullCurrentPath, fullNewPath); } return(DokanResult.Success); } else if (replace) { // Delete the original if possible, but otherwise ignore it when it's in another layer as it doesn't affect the external behaviour of the VFS // replace is incompatible with directories if (info.IsDirectory) { return(DokanResult.AccessDenied); } if (ControlsFile(newPath)) { try { File.Delete(fullNewPath); File.Move(fullCurrentPath, fullNewPath); return(DokanResult.Success); } catch (UnauthorizedAccessException) { return(DokanResult.AccessDenied); } } else { // A predecessor source has the file to be replaced, so we can ignore it. try { File.Move(fullCurrentPath, fullNewPath); return(DokanResult.Success); } catch (UnauthorizedAccessException) { return(DokanResult.AccessDenied); } } } else { return(DokanResult.FileExists); } } catch (UnauthorizedAccessException) { return(DokanResult.AccessDenied); } } else { return(PredecessorMoveFile(currentPath, newPath, replace, info)); } }
/// <summary> /// To be called once all file handles for this context have been closed and released. /// </summary> /// <param name="path">The path to the file</param> /// <param name="info">The information for the context of this operation</param> /// <returns>True if the operation was successful</returns> public bool CloseFileHandle(string path, LVFSContextInfo info) { return(Last.CloseFileHandle(path, info)); }
/// <inheritdoc/> public override NtStatus CreateFileHandle(string path, DokanNet.FileAccess access, FileShare share, FileMode mode, FileOptions options, FileAttributes attributes, LVFSContextInfo info) { var controlsFile = ControlsFile(path); if (!controlsFile && PredecessorHasFile(path)) { return(PredecessorCreateFileHandle(path, access, share, mode, options, attributes, info)); } var convertedPath = ConvertPath(path); var directoryExists = Directory.Exists(convertedPath); var fileExists = File.Exists(convertedPath); if (info.IsDirectory) { try { switch (mode) { case FileMode.Open: { if (directoryExists) { return(DokanResult.Success); } else if (fileExists) { return(NtStatus.NotADirectory); } else { return(PredecessorCreateFileHandle(path, access, share, mode, options, attributes, info)); } } case FileMode.CreateNew: { if (directoryExists) { return(DokanResult.AlreadyExists); } else if (fileExists) { return(DokanResult.FileExists); } else if (PredecessorHasFile(path)) { return(DokanResult.AlreadyExists); } else { Directory.CreateDirectory(convertedPath); return(DokanResult.Success); } } case FileMode.OpenOrCreate: { if (directoryExists) { return(DokanResult.Success); } else if (fileExists) { return(NtStatus.NotADirectory); } else if (PredecessorHasFile(path)) { return(PredecessorCreateFileHandle(path, access, share, mode, options, attributes, info)); } else { Directory.CreateDirectory(convertedPath); return(DokanResult.Success); } } default: { // I don't think any other file modes can actually used with directories, so we should be free to die in any arbitrary way here. In fact, I don't think OpenOrCreate can actually be used with directories either, but the associated behaviour was simple enough, so I implemented it anyway. return(DokanResult.NotImplemented); } } } catch (UnauthorizedAccessException) { return(DokanResult.AccessDenied); } } else { switch (mode) { case FileMode.Open: { if (fileExists || directoryExists) { var dataAccess = DokanNet.FileAccess.ReadData | DokanNet.FileAccess.WriteData | DokanNet.FileAccess.AppendData | DokanNet.FileAccess.Execute | DokanNet.FileAccess.GenericExecute | DokanNet.FileAccess.GenericWrite | DokanNet.FileAccess.GenericRead; var readWriteOnlyAttributes = (access & dataAccess) == 0; if (directoryExists || readWriteOnlyAttributes) { if (directoryExists && access.HasFlag(DokanNet.FileAccess.Delete) && !access.HasFlag(DokanNet.FileAccess.Synchronize)) { // Delete request on (potentially) non-empty directory return(DokanResult.AccessDenied); } info.IsDirectory = directoryExists; return(DokanResult.Success); } else { // Go to the regular handler break; } } else { return(PredecessorCreateFileHandle(path, access, share, mode, options, attributes, info)); } } case FileMode.OpenOrCreate: case FileMode.Create: case FileMode.Append: { if (fileExists || directoryExists) { // Go to the regular handler break; } else if (PredecessorHasFile(path)) { return(PredecessorCreateFileHandle(path, access, share, mode, options, attributes, info)); } else { // Go to the regular handler break; } } case FileMode.CreateNew: { if (fileExists || directoryExists || PredecessorHasFile(path)) { return(DokanResult.AlreadyExists); } else { // Go to the regular handler break; } } case FileMode.Truncate: { if (fileExists || directoryExists) { // Go to the regular handler break; } else if (PredecessorHasFile(path)) { return(PredecessorCreateFileHandle(path, access, share, mode, options, attributes, info)); } else { return(DokanResult.FileNotFound); } } default: // This code should never be reached throw new ArgumentException("Unknown file mode: " + mode, nameof(mode)); } var dataWriteAccess = DokanNet.FileAccess.WriteData | DokanNet.FileAccess.AppendData | DokanNet.FileAccess.Delete | DokanNet.FileAccess.GenericWrite; var readAccessOnly = (access & dataWriteAccess) == 0; try { var result = DokanResult.Success; if (!Directory.Exists(Path.GetDirectoryName(convertedPath))) { if (PredecessorHasDirectory(Path.GetDirectoryName(path))) { Directory.CreateDirectory(Path.GetDirectoryName(convertedPath)); } else { return(DokanResult.PathNotFound); } } info.Context[this] = new FileStream(convertedPath, mode, readAccessOnly ? System.IO.FileAccess.Read : System.IO.FileAccess.ReadWrite, share, 4096, options); if ((fileExists || directoryExists) && (mode == FileMode.OpenOrCreate || mode == FileMode.Create)) { result = DokanResult.AlreadyExists; } if (mode == FileMode.CreateNew || mode == FileMode.Create) { attributes |= FileAttributes.Archive; } File.SetAttributes(convertedPath, attributes); return(result); } catch (UnauthorizedAccessException) { return(DokanResult.AccessDenied); } catch (DirectoryNotFoundException) { return(DokanResult.PathNotFound); } catch (Exception ex) { var hr = (uint)System.Runtime.InteropServices.Marshal.GetHRForException(ex); switch (hr) { case 0x80070020: //Sharing violation return(DokanResult.SharingViolation); default: throw; } } } }
/// <summary> /// Gets the contents of the specified file starting at the specified offset and attempts to fill the buffer. /// </summary> /// <param name="path">The path to the file</param> /// <param name="buffer">The buffer to fill with the file contents</param> /// <param name="bytesRead">The actual number of bytes read from the file. This may be less than the length of the buffer if not enough data is available.</param> /// <param name="offset">The byte at which to start reading.</param> /// <param name="info">Holds the context for the operation and relevant information</param> /// <returns>A bool indicating whether the operation was successful</returns> public bool ReadFile(string path, byte[] buffer, out int bytesRead, long offset, LVFSContextInfo info) { return(Last.ReadFile(path, buffer, out bytesRead, offset, info)); }
/// <inheritdoc/> public override bool TryUnlockFileRegion(string path, long startOffset, long length, LVFSContextInfo info) { if (info.Context.ContainsKey(this)) { try { (info.Context[this] as FileStream)?.Unlock(startOffset, length); return(true); } catch (IOException) { return(false); } } else { return(PredecessorTryUnlockFileRegion(path, startOffset, length, info)); } }
/// <summary> /// Unlocks a region of the specified file from the specified offset with the specified length if possible. /// </summary> /// <param name="path">The path to the file</param> /// <param name="startOffset">The offset at which the region to unlock starts</param> /// <param name="length">The length of the region to unlock</param> /// <param name="info">Holds the context for the operation and relevant information</param> /// <returns>True if the operation was successful, false if access was denied</returns> public bool TryUnlockFileRegion(string path, long startOffset, long length, LVFSContextInfo info) { return(Last.TryUnlockFileRegion(path, startOffset, length, info)); }
/// <inheritdoc/> public override NtStatus WriteFile(string path, byte[] buffer, out int bytesWritten, long offset, LVFSContextInfo info) { if (info.Context.ContainsKey(this) && info.Context[this] != null) { try { var stream = info.Context[this] as FileStream; lock (stream) { stream.Position = offset; stream.Write(buffer, 0, buffer.Length); } bytesWritten = buffer.Length; return(DokanResult.Success); } catch (UnauthorizedAccessException) { bytesWritten = 0; return(DokanResult.AccessDenied); } catch (IOException) { bytesWritten = 0; return(DokanResult.DiskFull); } } else if (ControlsFile(path)) { using (var stream = new FileStream(ConvertPath(path), FileMode.Open, System.IO.FileAccess.Write)) { try { stream.Position = offset; stream.Write(buffer, 0, buffer.Length); bytesWritten = buffer.Length; } catch (UnauthorizedAccessException) { bytesWritten = 0; return(DokanResult.AccessDenied); } catch (IOException) { bytesWritten = 0; return(DokanResult.DiskFull); } } return(DokanResult.Success); } else { return(PredecessorWriteFile(path, buffer, out bytesWritten, offset, info)); } }
/// <summary> /// Writes the contents of the buffer to the requested file, starting at the requested offset, and sets the bytes written value to the number of bytes successfully written to the file. /// </summary> /// <param name="path">The path to the file</param> /// <param name="buffer">A buffer containing the data to write</param> /// <param name="bytesWritten">The number of bytes transferred from the buffer to the file</param> /// <param name="offset">The offset at which to start the write</param> /// <param name="info">Information concerning the context of this operation.</param> /// <returns><see cref="DokanResult.Success"/> if the operation was successful. If not, an appropriate error status.</returns> public NtStatus WriteFile(string path, byte[] buffer, out int bytesWritten, long offset, LVFSContextInfo info) { WritableSource writable = Last as WritableSource; if (writable != null) { return(writable.WriteFile(path, buffer, out bytesWritten, offset, info)); } else { bytesWritten = 0; return(DokanResult.AccessDenied); } }