222 lines
7.4 KiB
C#
222 lines
7.4 KiB
C#
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}";
|
|
}
|
|
}
|
|
}
|
|
}
|