Updated
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace EonaCat.LogStack.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class for managing directory permissions and ensuring write access.
|
||||
/// Handles both Windows and Unix-like systems gracefully.
|
||||
/// </summary>
|
||||
public static class DirectoryPermissionHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the current process has write access to the specified directory.
|
||||
/// Returns true if writable, false otherwise. Never throws.
|
||||
/// </summary>
|
||||
public static bool CanWrite(string dirPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dirPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(dirPath))
|
||||
{
|
||||
// Check if we can create it
|
||||
var parentPath = Path.GetDirectoryName(dirPath);
|
||||
if (string.IsNullOrEmpty(parentPath) || parentPath == dirPath)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return CanWrite(parentPath);
|
||||
}
|
||||
|
||||
// Try to create a temporary file to verify write access
|
||||
var testFile = Path.Combine(dirPath, ".permission_test_" + Guid.NewGuid().ToString("."));
|
||||
try
|
||||
{
|
||||
using (var fs = File.Create(testFile, 1, FileOptions.DeleteOnClose))
|
||||
{
|
||||
fs.WriteByte(0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to create a directory and fix permissions if needed.
|
||||
/// Never throws - logs errors and returns false if it cannot fix permissions.
|
||||
/// </summary>
|
||||
public static bool EnsureDirectory(string dirPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dirPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Create directory if it doesn't exist
|
||||
if (!Directory.Exists(dirPath))
|
||||
{
|
||||
Directory.CreateDirectory(dirPath);
|
||||
}
|
||||
|
||||
// Verify we have write access
|
||||
if (CanWrite(dirPath))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try to fix permissions (Windows-specific via attribute clearing)
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
try
|
||||
{
|
||||
TryFixWindowsPermissions(dirPath);
|
||||
return CanWrite(dirPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Permission fixing failed, but that's okay - we tried
|
||||
}
|
||||
}
|
||||
|
||||
// For Unix-like systems, we can't easily fix permissions without external tools
|
||||
// Just return whether we can write
|
||||
return CanWrite(dirPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to ensure all parent directories have write access.
|
||||
/// Creates missing directories.
|
||||
/// </summary>
|
||||
public static bool EnsureDirectoryHierarchy(string dirPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dirPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var current = Path.GetDirectoryName(dirPath);
|
||||
var stack = new System.Collections.Generic.Stack<string>();
|
||||
|
||||
// Build directory hierarchy
|
||||
while (!string.IsNullOrEmpty(current) && current != Path.GetPathRoot(current))
|
||||
{
|
||||
if (Directory.Exists(current))
|
||||
{
|
||||
break;
|
||||
}
|
||||
stack.Push(current);
|
||||
current = Path.GetDirectoryName(current);
|
||||
}
|
||||
|
||||
// Create missing directories from root to target
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
var dir = stack.Pop();
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure final target directory
|
||||
return EnsureDirectory(dirPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to grant write permissions on Windows by clearing the read-only attribute.
|
||||
/// This is a best-effort operation and may fail on restricted systems.
|
||||
/// </summary>
|
||||
private static void TryFixWindowsPermissions(string dirPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dirInfo = new DirectoryInfo(dirPath);
|
||||
|
||||
// Clear read-only attribute if set
|
||||
if ((dirInfo.Attributes & FileAttributes.ReadOnly) != 0)
|
||||
{
|
||||
dirInfo.Attributes &= ~FileAttributes.ReadOnly;
|
||||
}
|
||||
|
||||
// Note: Full ACL manipulation would require System.Security.AccessControl
|
||||
// which is a separate NuGet package. We're using the basic attribute approach
|
||||
// which covers most common permission issues.
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Silently fail - we tried our best
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a safe description of why directory access failed.
|
||||
/// </summary>
|
||||
public static string GetAccessIssueDiagnosis(string dirPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(dirPath))
|
||||
{
|
||||
return "Directory path is null or empty";
|
||||
}
|
||||
|
||||
if (!Directory.Exists(dirPath))
|
||||
{
|
||||
var parent = Path.GetDirectoryName(dirPath);
|
||||
if (string.IsNullOrEmpty(parent))
|
||||
{
|
||||
return $"Cannot determine parent directory: {dirPath}";
|
||||
}
|
||||
if (!Directory.Exists(parent))
|
||||
{
|
||||
return $"Parent directory does not exist: {parent}";
|
||||
}
|
||||
return $"Directory does not exist and parent is not writable: {dirPath}";
|
||||
}
|
||||
|
||||
var dirInfo = new DirectoryInfo(dirPath);
|
||||
|
||||
if ((dirInfo.Attributes & FileAttributes.ReadOnly) != 0)
|
||||
{
|
||||
return $"Directory is read-only: {dirPath}";
|
||||
}
|
||||
|
||||
return $"Directory exists but no write permission: {dirPath}. Check file system permissions.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"Error diagnosing directory: {ex.Message}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user