修复登录自启的权限问题:低完整性目录、日志兜底与数据目录自检

登录任务本身没问题(InteractiveToken + HighestAvailable + --minimized),
ICACLS 也没有拒绝 ACE;真因是 exe 所在的 D:\dsh workspace 带 Low 强制标签,
Windows 用低完整性令牌运行这里的 exe,它写不了中完整性的 %LOCALAPPDATA%,
于是第一步写日志就 UnauthorizedAccessException,托盘起不来。

- FileLog 改为 fail-safe:首选目录失败依次回退 LocalLow / TEMP / ProgramData,
  缓存待写行,永不抛异常,暴露 ActiveDirectory 与 LastFailure;
- 启动自检增加数据目录可写性探测,致命对话框写出真实日志目录与低完整性判断;
- 新增 --logcheck 只读自检:输出程序路径、数据目录可写性、实际日志目录与结论;
- README 增加放置目录说明,validation.md 记录第八轮;
- 新增两条 FileLog 回归测试(本地 49/49,探测 16/16,Release 0 警告 0 错误)。
This commit is contained in:
chuzhongzai
2026-10-24 11:18:09 +08:00
parent 7be117057a
commit 545d96174d
6 changed files with 222 additions and 10 deletions
@@ -0,0 +1,43 @@
using GatewayKeepalive.Core;
namespace GatewayKeepalive.Tests;
internal static class LogFallbackTests
{
public static Task WritablePrimaryDirectoryIsUsedAsync()
{
using var temp = new TempDirectory();
var target = Path.Combine(temp.DirectoryPath, "logs");
var log = new FileLog(target);
log.Write("测试:首选目录可写");
Check.Equal(Path.GetFullPath(target), Path.GetFullPath(log.ActiveDirectory), "A writable primary directory must stay active");
Check.True(log.LastFailure is null, "No failure should be recorded when the primary directory works");
var file = Path.Combine(target, $"gateway-{DateTime.Now:yyyy-MM-dd}.log");
Check.True(File.Exists(file), "The log file should exist in the primary directory");
Check.Contains("测试:首选目录可写", File.ReadAllText(file), "The line should land in the primary log");
return Task.CompletedTask;
}
public static Task UnusablePrimaryDirectoryFallsBackAsync()
{
using var temp = new TempDirectory();
var blocked = Path.Combine(temp.DirectoryPath, "blocked");
File.WriteAllText(blocked, "a file, so no directory can be created here");
var log = new FileLog(blocked);
log.Write("测试:日志回退");
Check.True(
!string.Equals(Path.GetFullPath(blocked), Path.GetFullPath(log.ActiveDirectory), StringComparison.OrdinalIgnoreCase),
"An unusable primary directory must be replaced by a fallback");
Check.True(log.LastFailure is not null, "The unusable primary directory should be reported as a failure");
Check.Contains("blocked", log.LastFailure!, "The failure text should name the unusable directory");
var fallback = Path.Combine(log.ActiveDirectory, $"gateway-{DateTime.Now:yyyy-MM-dd}.log");
Check.True(File.Exists(fallback), "The line should be written to the fallback directory");
Check.Contains("测试:日志回退", File.ReadAllText(fallback), "The line should be present in the fallback log");
return Task.CompletedTask;
}
}
+2
View File
@@ -50,6 +50,8 @@ internal static class Program
("NetworkComparison: every owned route, DNS, and network field participates", NetworkComparisonTests.EachOwnedFieldParticipatesAsync),
("NetworkComparison: transaction recovery accepts only exact before/intended rows", NetworkComparisonTests.RecoveryRejectsForeignRowsAsync),
("JsonStore: round trip, backup, and failed serialization preserve the last complete file", JsonStoreTests.AtomicReplacementAsync),
("FileLog: a writable primary directory is used and reports no failure", LogFallbackTests.WritablePrimaryDirectoryIsUsedAsync),
("FileLog: an unusable primary directory falls back instead of throwing", LogFallbackTests.UnusablePrimaryDirectoryFallsBackAsync),
("Windows NativeLayout: x64 structure sizes and metric offsets", NativeLayoutTests.X64LayoutAsync),
("LoginStartup: logon task XML starts minimized with highest privileges", LoginStartupTests.LogonTaskXmlAsync),
("LoginStartup: missing-task detection follows HRESULT, not exception type", LoginStartupTests.TaskMissingDetectionAsync),