Files
community/Ink Canvas/Helpers/ProcessProtectionManager.cs
T
2026-02-21 17:44:35 +08:00

474 lines
16 KiB
C#

using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
namespace Ink_Canvas.Helpers
{
internal static class ProcessProtectionManager
{
private static readonly object _lock = new object();
private static readonly Dictionary<string, FileStream> _lockedFiles = new Dictionary<string, FileStream>(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<string, SafeFileHandle> _lockedDirs = new Dictionary<string, SafeFileHandle>(StringComparer.OrdinalIgnoreCase);
private static bool _enabled;
private static int _writeGate;
private static readonly string[] _excludedSubDirectories = new[]
{
"Configs",
"Saves",
"Backups",
"Logs",
"AutoUpdate"
};
public static bool Enabled
{
get { lock (_lock) return _enabled; }
}
public static void ApplyFromSettings()
{
try
{
var settings = MainWindow.Settings;
var enabled = settings?.Security != null && settings.Security.EnableProcessProtection;
SetEnabled(enabled);
}
catch (Exception ex)
{
LogHelper.WriteLogToFile($"ProcessProtectionManager.ApplyFromSettings 失败: {ex.Message}", LogHelper.LogType.Warning);
}
}
public static void SetEnabled(bool enabled)
{
lock (_lock)
{
if (_enabled == enabled) return;
_enabled = enabled;
}
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
if (enabled) Enable();
else Disable();
}
catch (Exception ex)
{
try
{
LogHelper.WriteLogToFile($"ProcessProtectionManager.SetEnabled 后台执行失败: {ex.Message}", LogHelper.LogType.Warning);
}
catch { }
}
});
}
public static void WithWriteAccess(string targetPath, Action action)
{
if (action == null) return;
if (!Enabled)
{
action();
return;
}
const int gateTimeoutMs = 10_000;
if (!TryEnterWriteGate(gateTimeoutMs))
{
try
{
LogHelper.WriteLogToFile($"ProcessProtectionManager.WithWriteAccess: 获取写入门闩超时({gateTimeoutMs}ms),将降级释放目标路径锁后执行写入。目标: {targetPath}",
LogHelper.LogType.Warning);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[ProcessProtectionManager] 写日志失败: {ex.Message}");
}
var normPath = NormalizePath(targetPath);
var dirsChain = GetDirChainToRoot(normPath);
Dictionary<string, SafeFileHandle> fallbackDirs = null;
Dictionary<string, FileStream> fallbackFiles = null;
try
{
lock (_lock)
{
fallbackDirs = new Dictionary<string, SafeFileHandle>(StringComparer.OrdinalIgnoreCase);
fallbackFiles = new Dictionary<string, FileStream>(StringComparer.OrdinalIgnoreCase);
foreach (var dir in dirsChain)
{
if (_lockedDirs.TryGetValue(dir, out var handle))
{
_lockedDirs.Remove(dir);
fallbackDirs[dir] = handle;
}
}
if (!string.IsNullOrWhiteSpace(normPath) && File.Exists(normPath) && _lockedFiles.TryGetValue(normPath, out var fs))
{
_lockedFiles.Remove(normPath);
fallbackFiles[normPath] = fs;
}
}
if (fallbackFiles != null)
{
foreach (var kv in fallbackFiles)
{
try { kv.Value.Dispose(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
}
}
if (fallbackDirs != null)
{
foreach (var kv in fallbackDirs)
{
try { kv.Value.Dispose(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
}
}
action();
}
finally
{
try
{
if (Enabled)
{
Enable(rescanRoot: false, rescanDirs: dirsChain);
}
}
catch { }
}
return;
}
var normalized = NormalizePath(targetPath);
var dirsToToggle = GetDirChainToRoot(normalized);
Dictionary<string, SafeFileHandle> releasedDirs = null;
Dictionary<string, FileStream> releasedFiles = null;
try
{
lock (_lock)
{
releasedDirs = new Dictionary<string, SafeFileHandle>(StringComparer.OrdinalIgnoreCase);
releasedFiles = new Dictionary<string, FileStream>(StringComparer.OrdinalIgnoreCase);
foreach (var dir in dirsToToggle)
{
if (_lockedDirs.TryGetValue(dir, out var handle))
{
_lockedDirs.Remove(dir);
releasedDirs[dir] = handle;
}
}
if (!string.IsNullOrWhiteSpace(normalized) && File.Exists(normalized) && _lockedFiles.TryGetValue(normalized, out var fs))
{
_lockedFiles.Remove(normalized);
releasedFiles[normalized] = fs;
}
}
if (releasedFiles != null)
{
foreach (var kv in releasedFiles)
{
try { kv.Value.Dispose(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
}
}
if (releasedDirs != null)
{
foreach (var kv in releasedDirs)
{
try { kv.Value.Dispose(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
}
}
action();
}
finally
{
try
{
if (Enabled)
{
Enable(rescanRoot: false, rescanDirs: dirsToToggle);
}
}
catch
{
}
Interlocked.Exchange(ref _writeGate, 0);
}
}
private static bool TryEnterWriteGate(int timeoutMs)
{
if (timeoutMs <= 0) timeoutMs = 1;
var start = Environment.TickCount;
while (Interlocked.CompareExchange(ref _writeGate, 1, 0) != 0)
{
var elapsed = unchecked(Environment.TickCount - start);
if (elapsed >= timeoutMs) return false;
if (elapsed < 2000) Thread.Sleep(10);
else Thread.Sleep(50);
}
return true;
}
private static void Enable()
{
Enable(rescanRoot: true, rescanDirs: null);
}
private static void Enable(bool rescanRoot, IEnumerable<string> rescanDirs)
{
try
{
var root = App.RootPath;
if (string.IsNullOrWhiteSpace(root) || !Directory.Exists(root)) return;
root = NormalizePath(root);
if (rescanRoot)
{
LockDirectoryRecursive(root);
}
else if (rescanDirs != null)
{
foreach (var d in rescanDirs)
{
if (Directory.Exists(d))
{
LockDirectory(d);
}
}
}
if (rescanRoot)
{
LockFilesRecursive(root);
}
else if (rescanDirs != null)
{
foreach (var d in rescanDirs)
{
if (Directory.Exists(d))
{
LockFilesRecursive(d);
}
else if (File.Exists(d))
{
LockFile(d);
}
}
}
}
catch
{
}
}
private static void Disable()
{
lock (_lock)
{
foreach (var kv in _lockedFiles)
{
try { kv.Value.Dispose(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
}
_lockedFiles.Clear();
foreach (var kv in _lockedDirs)
{
try { kv.Value.Dispose(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
}
_lockedDirs.Clear();
}
}
private static void LockDirectoryRecursive(string root)
{
try
{
if (!IsExcludedPath(root))
{
LockDirectory(root);
}
foreach (var dir in Directory.GetDirectories(root, "*", SearchOption.AllDirectories))
{
if (!IsExcludedPath(dir))
{
LockDirectory(dir);
}
}
}
catch
{
}
}
private static void LockFilesRecursive(string root)
{
try
{
foreach (var file in Directory.GetFiles(root, "*", SearchOption.AllDirectories))
{
if (!IsExcludedPath(file))
{
var ext = Path.GetExtension(file);
if (string.Equals(ext, ".exe", StringComparison.OrdinalIgnoreCase) ||
string.Equals(ext, ".dll", StringComparison.OrdinalIgnoreCase) ||
string.Equals(ext, ".config", StringComparison.OrdinalIgnoreCase) ||
string.Equals(ext, ".manifest", StringComparison.OrdinalIgnoreCase) ||
string.Equals(ext, ".dat", StringComparison.OrdinalIgnoreCase) ||
string.Equals(ext, ".enc", StringComparison.OrdinalIgnoreCase))
{
LockFile(file);
}
}
}
}
catch
{
}
}
private static void LockFile(string filePath)
{
filePath = NormalizePath(filePath);
lock (_lock)
{
if (_lockedFiles.ContainsKey(filePath)) return;
try
{
var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
_lockedFiles[filePath] = fs;
}
catch
{
}
}
}
private static void LockDirectory(string dirPath)
{
dirPath = NormalizePath(dirPath);
lock (_lock)
{
if (_lockedDirs.ContainsKey(dirPath)) return;
try
{
var handle = CreateDirectoryHandle(dirPath);
if (handle != null && !handle.IsInvalid)
{
_lockedDirs[dirPath] = handle;
}
}
catch
{
}
}
}
private static string NormalizePath(string p)
{
try
{
if (string.IsNullOrWhiteSpace(p)) return p;
return Path.GetFullPath(p.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
}
catch
{
return p;
}
}
private static List<string> GetDirChainToRoot(string path)
{
var list = new List<string>();
try
{
var root = NormalizePath(App.RootPath);
if (string.IsNullOrWhiteSpace(root)) return list;
string dir = Directory.Exists(path) ? NormalizePath(path) : NormalizePath(Path.GetDirectoryName(path));
while (!string.IsNullOrWhiteSpace(dir))
{
if (!dir.StartsWith(root, StringComparison.OrdinalIgnoreCase)) break;
list.Add(dir);
if (string.Equals(dir, root, StringComparison.OrdinalIgnoreCase)) break;
dir = NormalizePath(Path.GetDirectoryName(dir));
}
}
catch
{
}
return list;
}
private static bool IsExcludedPath(string path)
{
try
{
var root = NormalizePath(App.RootPath);
if (string.IsNullOrWhiteSpace(root)) return false;
path = NormalizePath(path);
foreach (var name in _excludedSubDirectories)
{
var prefix = Path.Combine(root, name);
if (path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
catch
{
}
return false;
}
private static SafeFileHandle CreateDirectoryHandle(string dirPath)
{
const uint GENERIC_READ = 0x80000000;
const uint FILE_SHARE_READ = 0x00000001;
const uint OPEN_EXISTING = 3;
const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
return CreateFile(
dirPath,
GENERIC_READ,
FILE_SHARE_READ,
IntPtr.Zero,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
IntPtr.Zero);
}
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern SafeFileHandle CreateFile(
string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
IntPtr lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
IntPtr hTemplateFile);
}
}