chore(messages): VET 运行时中文字符串英文化

把 VET 下 10 个文件的运行时中文字符串(CLI 用法/帮助、扫描与校验日志、目录表与缓存诊断、PE 解析报错等)共 224 行译为英文。口径:只改字符串字面量内部文字,行数零变化(+224/-224);printf 格式符与转义序列多重集合校验不变(_strcheck 异常 0)。验证:编译 SUCCESS;VET.exe --search-paths 7 条 exit 0;VET.exe scan --cache <dir> --once 输出合法 JSON、exit 0。
This commit is contained in:
TermiNexus committed 2026-10-07 00:17:24 +08:00
1 parent 78a7897d30
commit 91a656f0b7
10 files changed
+224 -224

No files matched your search

+16 -16
View File
@@ -130,7 +130,7 @@ def ClearCache():
def CacheAdd(ExeAnsi: str, Id: str, Ver: str, Arch: str, Src: str, Disp: str) -> int:
global Cache_Count
if Cache_Count >= CACHE_CAP:
VLog.LogFmt1(" 警告: 缓存条目已达上限 %d,忽略新条目\n", sizet(CACHE_CAP))
VLog.LogFmt1(" Warning: cache entries reached limit %d, new entry ignored\n", sizet(CACHE_CAP))
return 0
E: str = VFs.DupStr(ExeAnsi)
if E is None:
@@ -183,7 +183,7 @@ def ValidateCache() -> int:
if VFs.PathIsFile(Cache_Exe[j]) != 0:
j += 1
else:
VLog.VerboseFmtS1(" [移除] 已不存在: %s\n", Cache_Exe[j])
VLog.VerboseFmtS1(" [remove] no longer exists: %s\n", Cache_Exe[j])
RemoveAt(j)
removed += 1
return removed
@@ -199,10 +199,10 @@ def WriteCacheFile(Path: str, Buf: bytes, Len: sizet) -> int:
return 0
rc: int = VFs.WriteFileAll(Path, Buf, Len)
if rc == 0:
VLog.LogFmt1(" 警告: 缓存写入失败(打开失败, win32 err=%u)\n", wb.GetLastError())
VLog.LogFmt1(" Warning: cache write failed (open failed, win32 err=%u)\n", wb.GetLastError())
return 0
if rc < 0:
VLog.LogFmt2(" 警告: 缓存写入不完整 (%zu/%zu 字节)\n", VFs.LastWriteDone, Len)
VLog.LogFmt2(" Warning: cache write incomplete (%zu/%zu bytes)\n", VFs.LastWriteDone, Len)
return 0
return 1
@@ -247,7 +247,7 @@ def ParseCacheText(Txt: str) -> int:
if L == 0:
return 0
if L > sizet(CACHE_TEXT_MAX):
VLog.LogFmt2("缓存: 文本 %zu 字节超出解析上限 %zu,按空缓存启动\n",
VLog.LogFmt2("cache: text %zu bytes exceeds parse limit %zu, starting with empty cache\n",
L, sizet(CACHE_TEXT_MAX))
return 0
# Pool capacity: worst case ~1 JsonValue node per input byte + key/value copies + temps, scaled
@@ -255,32 +255,32 @@ def ParseCacheText(Txt: str) -> int:
Cap: sizet = L * sizet(CACHE_ARENA_PER_BYTE) + sizet(CACHE_ARENA_SLACK)
Arena: bytes = stdlib.malloc(Cap)
if Arena is None:
VLog.LogStr("缓存: 解析工作池分配失败,按空缓存启动\n")
VLog.LogStr("cache: parse pool allocation failed, starting with empty cache\n")
return 0
Pool: memhub.MemPool* = memhub.MemPool(Arena, Cap)
if Pool is None:
stdlib.free(t.CPtr(Arena))
VLog.LogStr("缓存: 解析工作池构造失败,按空缓存启动\n")
VLog.LogStr("cache: parse pool construction failed, starting with empty cache\n")
return 0
# Pool precheck (fail-closed): the whole tree must fit, else json.parse's pool.alloc would
# exit(1). Need mirrors Cap, so this fails cleanly (empty-cache fallback) if Cap is too small.
Need: sizet = L * sizet(CACHE_ARENA_PER_BYTE) + sizet(CACHE_ARENA_SLACK)
if Pool.AllocOk(Need) == 0:
stdlib.free(t.CPtr(Arena))
VLog.LogFmt1("缓存: 解析工作池预检不足(最坏需要 %zu 字节),按空缓存启动\n",
VLog.LogFmt1("cache: parse pool precheck insufficient (worst case needs %zu bytes), starting with empty cache\n",
Need)
return 0
ConvBuf: bytes = _mbuddy.alloc(VFs.CONV_CAP)
if ConvBuf is None:
stdlib.free(t.CPtr(Arena))
VLog.LogStr("缓存: 编码转换缓冲分配失败,按空缓存启动\n")
VLog.LogStr("cache: encoding conversion buffer allocation failed, starting with empty cache\n")
return 0
Root: json.JsonValue* = json.parse(Pool, Txt)
added: int = 0
if Root == None:
VLog.LogStr("缓存: JSON 解析失败(内容损坏),按空缓存启动\n")
VLog.LogStr("cache: JSON parse failed (corrupt content), starting with empty cache\n")
elif Root.vtype != json.JSON_ARRAY:
VLog.LogStr("缓存: 顶层不是 JSON 数组,按空缓存启动\n")
VLog.LogStr("cache: top level is not a JSON array, starting with empty cache\n")
else:
cu: json.JsonValue* = Root.child
while cu != None:
@@ -299,17 +299,17 @@ def LoadCache(Path: str) -> int:
if buf is None:
rc_err: int = VFs.LastReadErr
if rc_err == 1:
VLog.VerboseFmtS1("缓存: 未找到/无法打开,按空缓存启动: %s\n", Path)
VLog.VerboseFmtS1("cache: not found / cannot open, starting with empty cache: %s\n", Path)
elif rc_err == 2:
VLog.LogStr("缓存: 读取大小失败,按空缓存启动\n")
VLog.LogStr("cache: read size failed, starting with empty cache\n")
elif rc_err == 5:
VLog.LogStr("缓存: 分配缓冲失败,按空缓存启动\n")
VLog.LogStr("cache: buffer allocation failed, starting with empty cache\n")
else:
VLog.LogStr("缓存: 大小异常/过大,按空缓存启动\n")
VLog.LogStr("cache: size invalid / too large, starting with empty cache\n")
return 0
n: int = ParseCacheText(str(buf))
stdlib.free(t.CPtr(buf))
VLog.VerboseFmt1("缓存: 已载入 %u 项种子\n", sizet(n))
VLog.VerboseFmt1("cache: loaded %u seed entries\n", sizet(n))
return n
+14 -14
View File
@@ -158,22 +158,22 @@ def CheckInit() -> int:
Check_Conv = _mbuddy.alloc(VFs.CONV_CAP)
Check_Arena = stdlib.malloc(CK_REPLY_POOL_CAP)
if EnsurePathBuf() == 0:
VLog.LogStr(" 警告: check 缓冲分配失败(请求路径缓冲),check 通道不可用\n")
VLog.LogStr(" Warning: check buffer allocation failed (request path buffer), check channel unavailable\n")
return 0
if Check_Ver is None or Check_Arch is None or Check_Probe is None:
VLog.LogStr(" 警告: check 缓冲分配失败(版本/arch/探测缓冲),check 通道不可用\n")
VLog.LogStr(" Warning: check buffer allocation failed (version/arch/probe buffer), check channel unavailable\n")
return 0
if Check_Msg is None or Check_Id is None:
VLog.LogStr(" 警告: check 缓冲分配失败(原因串/UUID 缓冲),check 通道不可用\n")
VLog.LogStr(" Warning: check buffer allocation failed (reason string/UUID buffer), check channel unavailable\n")
return 0
if Check_Disp is None or Check_Conv is None or Check_Arena is None:
VLog.LogStr(" 警告: check 缓冲分配失败(展示串/转换缓冲/回执 arena),check 通道不可用\n")
VLog.LogStr(" Warning: check buffer allocation failed (display string/conversion buffer/reply arena), check channel unavailable\n")
return 0
# Construct the pool in place on the heap (memhub.NewMemPool): Check_Pool outlives this frame
# (same reason as VJson.JsonInit).
Pool: memhub.MemPool* = memhub.NewMemPool(Check_Arena, CK_REPLY_POOL_CAP)
if Pool is None:
VLog.LogStr(" 警告: check 回执工作池构造失败,check 通道不可用\n")
VLog.LogStr(" Warning: check reply pool construction failed, check channel unavailable\n")
return 0
Check_Pool = Pool
Check_Ready = 1
@@ -337,7 +337,7 @@ def TrackChecked(PathAnsi: str) -> int:
return 1
if VCache.CacheAdd(PathAnsi, str(Check_Id), str(Check_Ver), str(Check_Arch),
CK_SOURCE, str(Check_Disp)) == 0:
VLog.LogStr(" 警告: check 命中但入库失败(容量已满或分配失败),本轮不跟踪该条目\n")
VLog.LogStr(" Warning: check hit but cache insert failed (capacity full or allocation failed), entry not tracked this round\n")
return 0
return 1
@@ -380,7 +380,7 @@ def EmitReplyOk(ReqIdVal: LONGLONG, PathAnsi: str):
else:
# new hit: generate a UUID then insert (failure only warns; the reply still reports the result)
if VGuid.NewGuidStr(Check_Id, VGuid.GUID_STR_CAP) == 0:
VLog.LogStr(" 警告: CoCreateGuid 失败,check 入库项的 id 为空串\n")
VLog.LogStr(" Warning: CoCreateGuid failed, id of the check cache entry is empty\n")
Check_Id[0] = 0
Id = str(Check_Id)
TrackChecked(PathAnsi)
@@ -389,17 +389,17 @@ def EmitReplyOk(ReqIdVal: LONGLONG, PathAnsi: str):
ValNeed = ValNeed + string.strlen(Id) + string.strlen(Ver) + string.strlen(Arch)
ValNeed = ValNeed + string.strlen(CK_SOURCE)
if Check_Pool.AllocOk(sizet(CK_REPLY_NEED_FIXED) + ValNeed) == 0:
VLog.LogStr(" 警告: check 回执工作池不足,未回复(不会写穿缓冲)\n")
VLog.LogStr(" Warning: check reply pool insufficient, not replied (buffer will not be overrun)\n")
return
Obj: json.JsonValue* = json.object(Check_Pool)
if Obj == None:
VLog.LogStr(" 警告: check 回执对象分配失败,未回复\n")
VLog.LogStr(" Warning: check reply object allocation failed, not replied\n")
return
json.object_set(Check_Pool, Obj, "check_result", json.bool_val(Check_Pool, True))
json.object_set(Check_Pool, Obj, "id", json.int_val(Check_Pool, int64(ReqIdVal)))
Sub: json.JsonValue* = json.object(Check_Pool)
if Sub == None:
VLog.LogStr(" 警告: check 回执对象分配失败,未回复\n")
VLog.LogStr(" Warning: check reply object allocation failed, not replied\n")
return
json.object_set(Check_Pool, Sub, "id", json.string_val(Check_Pool, Id))
# convert executable / displayName to UTF-8 and hand to the library immediately (Check_Conv is reused)
@@ -415,7 +415,7 @@ def EmitReplyOk(ReqIdVal: LONGLONG, PathAnsi: str):
json.object_set(Check_Pool, Obj, "viper", Sub)
Txt: str = json.write(Check_Pool, Obj, False)
if Txt == None:
VLog.LogStr(" 警告: check 回执序列化失败(工作池不足),未回复\n")
VLog.LogStr(" Warning: check reply serialization failed (pool insufficient), not replied\n")
return
WriteReplyLine(Txt)
@@ -427,18 +427,18 @@ def EmitReplyFail(ReqIdVal: LONGLONG, Msg: str):
if Msg != None:
Need = Need + string.strlen(Msg)
if Check_Pool.AllocOk(Need) == 0:
VLog.LogStr(" 警告: check 回执工作池不足,未回复(不会写穿缓冲)\n")
VLog.LogStr(" Warning: check reply pool insufficient, not replied (buffer will not be overrun)\n")
return
Obj: json.JsonValue* = json.object(Check_Pool)
if Obj == None:
VLog.LogStr(" 警告: check 回执对象分配失败,未回复\n")
VLog.LogStr(" Warning: check reply object allocation failed, not replied\n")
return
json.object_set(Check_Pool, Obj, "check_result", json.bool_val(Check_Pool, False))
json.object_set(Check_Pool, Obj, "id", json.int_val(Check_Pool, int64(ReqIdVal)))
json.object_set(Check_Pool, Obj, "check_msg", json.string_val(Check_Pool, Msg))
Txt: str = json.write(Check_Pool, Obj, False)
if Txt == None:
VLog.LogStr(" 警告: check 回执序列化失败(工作池不足),未回复\n")
VLog.LogStr(" Warning: check reply serialization failed (pool insufficient), not replied\n")
return
WriteReplyLine(Txt)
+52 -52
View File
@@ -205,35 +205,35 @@ def ParseUint64(S: str) -> int:
def PrintUsage():
VLog.LogStr("VET - Viper Environment Tool\n")
VLog.LogStr("用法:\n")
VLog.LogStr("Usage:\n")
VLog.LogStr(" VET.exe ver <exe> [--verbose]\n")
VLog.LogStr(" 单次校验指定 exe(常用目录表 + .PRVERSN 段 + --version 前缀);\n")
VLog.LogStr(" --verbose 为与 scan 同名的无值开关(输出目标路径与解析/探测细节)。\n")
VLog.LogStr(" VET.exe scan --cache <path> [--once] [--sleep <毫秒>] [--pace <毫秒>] [--pace-units <N>]\n")
VLog.LogStr(" [--first-no-pace] [--need-confirm <秒> [--need-confirm-timeout <毫秒>]]\n")
VLog.LogStr(" Validate one exe once (common directory table + .PRVERSN section + --version prefix);\n")
VLog.LogStr(" --verbose is the valueless switch shared with scan (prints the target path and parse/probe details).\n")
VLog.LogStr(" VET.exe scan --cache <path> [--once] [--sleep <ms>] [--pace <ms>] [--pace-units <N>]\n")
VLog.LogStr(" [--first-no-pace] [--need-confirm <sec> [--need-confirm-timeout <ms>]]\n")
VLog.LogStr(" [--verbose] [--path <dir>]...\n")
VLog.LogStr(" 常驻扫盘发现器;结果 JSON 写 stdout,缓存写 <path>。\n")
VLog.LogStr(" Resident disk scanner; result JSON goes to stdout, cache is written to <path>.\n")
VLog.LogStr(" VET.exe scan --search-paths [--path <dir>]... [--verbose]\n")
VLog.LogStr(" VET.exe --search-paths [--path <dir>]... [--verbose] (同上,可省 scan:纯查询)\n")
VLog.LogStr(" --once 只跑一轮(便于验证)。\n")
VLog.LogStr(" --sleep <毫秒> 每轮间隔(缺省 10000;0 = 不 sleep;上限 86400000)。\n")
VLog.LogStr(" --pace <毫秒> 节流点睡眠时长(缺省 20;0 = 关闭节流;上限 60000)。\n")
VLog.LogStr(" --pace-units <N> 每 N 个文件一个节流点(缺省 64;1 = 每个文件都停;上限 1000000)。\n")
VLog.LogStr(" 显式给出 --pace 或 --pace-units 即启用节流,--once 下也生效;\n")
VLog.LogStr(" 未给出时沿用缺省(常驻启用;--once 关闭)。目录侧粒度恒为 16。\n")
VLog.LogStr(" --first-no-pace 第 1 轮扫盘不启用节流(只影响第 1 轮;之后各轮按常规规则)。\n")
VLog.LogStr(" 与 --pace 0 的区别:后者是全程不限速。仅常驻多轮时才有意义。\n")
VLog.LogStr(" --need-confirm <秒> 心跳确认:独立看门狗子进程每 <秒> 秒要求 stdin 输入一行\n")
VLog.LogStr(" {\"ok\": 1};超时内未输入或 ok!=1 则输出 {\"exit\":1, ...}\n")
VLog.LogStr(" 并终止本进程(退出码 1)。缺省/0 = 关闭;仅 scan 模式生效。\n")
VLog.LogStr(" --need-confirm-timeout <毫秒>\n")
VLog.LogStr(" 确认等待超时(缺省 5000;仅在给出 --need-confirm 时有意义)。\n")
VLog.LogStr(" --verbose 输出详细过程日志(进度/扫描根/逐条过滤/探测细节);\n")
VLog.LogStr(" 缺省安静:只留启动摘要、心跳确认提示、警告与轮末一行。\n")
VLog.LogStr(" --path <dir> 强制替换常用目录表(可重复);一出现即不做任何自动建表。\n")
VLog.LogStr(" --search-paths 打印将搜索的路径后退出:建好常用目录表即逐行列出\n")
VLog.LogStr(" (序号 + 路径 + 目录表来源,输出到 stderr),不扫盘、\n")
VLog.LogStr(" 不拉起看门狗、不读写缓存;此时 --cache 非必填。\n")
VLog.LogStr(" VET.exe --search-paths [--path <dir>]... [--verbose] (same as above; scan may be omitted: query only)\n")
VLog.LogStr(" --once run a single round only (for verification).\n")
VLog.LogStr(" --sleep <ms> interval per round (default 10000; 0 = no sleep; max 86400000).\n")
VLog.LogStr(" --pace <ms> sleep duration at throttle points (default 20; 0 = disable throttling; max 60000).\n")
VLog.LogStr(" --pace-units <N> one throttle point every N files (default 64; 1 = stop at every file; max 1000000).\n")
VLog.LogStr(" giving --pace or --pace-units explicitly enables throttling, also under --once;\n")
VLog.LogStr(" when absent the defaults apply (on for resident; off under --once). directory granularity is always 16.\n")
VLog.LogStr(" --first-no-pace round 1 scans without throttling (affects round 1 only; later rounds follow normal rules).\n")
VLog.LogStr(" differs from --pace 0: the latter disables throttling for the whole run. meaningful only for multi-round resident mode.\n")
VLog.LogStr(" --need-confirm <sec> heartbeat confirmation: an independent watchdog child asks for one stdin line every <sec> seconds\n")
VLog.LogStr(" {\"ok\": 1}; if no input arrives in time or ok!=1, prints {\"exit\":1, ...}\n")
VLog.LogStr(" and terminates this process (exit code 1). default/0 = off; scan mode only.\n")
VLog.LogStr(" --need-confirm-timeout <ms>\n")
VLog.LogStr(" confirmation wait timeout (default 5000; meaningful only when --need-confirm is given).\n")
VLog.LogStr(" --verbose print detailed process logs (progress/scan roots/per-item filtering/probe details);\n")
VLog.LogStr(" quiet by default: only the startup summary, heartbeat prompts, warnings and one round-end line.\n")
VLog.LogStr(" --path <dir> force-replace the common directory table (repeatable); once present no auto table is built.\n")
VLog.LogStr(" --search-paths print the paths to search then exit: build the common directory table and list it line by line\n")
VLog.LogStr(" (index + path + table source, to stderr); no scan,\n")
VLog.LogStr(" no watchdog, no cache access; --cache is optional in this case.\n")
# Parse scan arguments; returns 0 = success, 1 = argument error (usage/error already printed).
@@ -266,7 +266,7 @@ def ParseScanArgs() -> int:
Cache_Path = sys._argv[i + 1]
i += 1
else:
VLog.LogStr("错误: --cache 需要一个路径参数\n")
VLog.LogStr("Error: --cache requires a path argument\n")
return 1
elif string.strcmp(A, "--once") == 0:
Once_Flag = 1
@@ -278,7 +278,7 @@ def ParseScanArgs() -> int:
if i + 1 < sys._argc and sys._argv[i + 1] is not None:
M: int = ParseUint(sys._argv[i + 1], SLEEP_MAX_MS)
if M < 0:
VLog.LogFmt1("错误: --sleep 需要一个 0..%u 的整数字面量毫秒数,实际为: ",
VLog.LogFmt1("Error: --sleep requires an integer-literal millisecond value in 0..%u, got: ",
sizet(SLEEP_MAX_MS))
VLog.LogFmtS1("%s\n", sys._argv[i + 1])
PrintUsage()
@@ -286,14 +286,14 @@ def ParseScanArgs() -> int:
Sleep_Ms = M
i += 1
else:
VLog.LogStr("错误: --sleep 需要一个毫秒数参数\n")
VLog.LogStr("Error: --sleep requires a millisecond argument\n")
PrintUsage()
return 1
elif string.strcmp(A, "--pace") == 0:
if i + 1 < sys._argc and sys._argv[i + 1] is not None:
P: int = ParseUint(sys._argv[i + 1], PACE_SLEEP_MAX_MS)
if P < 0:
VLog.LogFmt1("错误: --pace 需要一个 0..%u 的整数字面量毫秒数,实际为: ",
VLog.LogFmt1("Error: --pace requires an integer-literal millisecond value in 0..%u, got: ",
sizet(PACE_SLEEP_MAX_MS))
VLog.LogFmtS1("%s\n", sys._argv[i + 1])
PrintUsage()
@@ -302,7 +302,7 @@ def ParseScanArgs() -> int:
Pace_Set = 1 # explicitly given => throttling on (also under --once)
i += 1
else:
VLog.LogStr("错误: --pace 需要一个毫秒数参数\n")
VLog.LogStr("Error: --pace requires a millisecond argument\n")
PrintUsage()
return 1
elif string.strcmp(A, "--pace-units") == 0:
@@ -310,7 +310,7 @@ def ParseScanArgs() -> int:
U: int = ParseUint(sys._argv[i + 1], PACE_UNITS_MAX)
# 0 and invalid are both errors; 0 makes "a sleep point every 0 files" meaningless
if U <= 0:
VLog.LogFmt1("错误: --pace-units 需要一个 1..%u 的整数字面量文件数,实际为: ",
VLog.LogFmt1("Error: --pace-units requires an integer-literal file count in 1..%u, got: ",
sizet(PACE_UNITS_MAX))
VLog.LogFmtS1("%s\n", sys._argv[i + 1])
PrintUsage()
@@ -319,7 +319,7 @@ def ParseScanArgs() -> int:
Pace_Set = 1 # explicitly given => throttling on (also under --once)
i += 1
else:
VLog.LogStr("错误: --pace-units 需要一个文件数参数\n")
VLog.LogStr("Error: --pace-units requires a file-count argument\n")
PrintUsage()
return 1
elif string.strcmp(A, "--first-no-pace") == 0:
@@ -331,7 +331,7 @@ def ParseScanArgs() -> int:
S: int = ParseSecs(sys._argv[i + 1])
if S < 0:
# two fixed-arity calls on one line (LogFmt1 takes ints, LogFmtS1 takes strings)
VLog.LogFmt1("错误: --need-confirm 需要一个 0..%u 的整数字面量秒数,实际为: ",
VLog.LogFmt1("Error: --need-confirm requires an integer-literal second value in 0..%u, got: ",
sizet(SECS_MAX))
VLog.LogFmtS1("%s\n", sys._argv[i + 1])
PrintUsage()
@@ -339,14 +339,14 @@ def ParseScanArgs() -> int:
Need_Confirm = S
i += 1
else:
VLog.LogStr("错误: --need-confirm 需要一个秒数参数\n")
VLog.LogStr("Error: --need-confirm requires a second argument\n")
PrintUsage()
return 1
elif string.strcmp(A, "--need-confirm-timeout") == 0:
if i + 1 < sys._argc and sys._argv[i + 1] is not None:
T: int = ParseUint(sys._argv[i + 1], CONFIRM_TIMEOUT_MAX_MS)
if T < 0:
VLog.LogFmt1("错误: --need-confirm-timeout 需要一个 0..%u 的整数字面量毫秒数,实际为: ",
VLog.LogFmt1("Error: --need-confirm-timeout requires an integer-literal millisecond value in 0..%u, got: ",
sizet(CONFIRM_TIMEOUT_MAX_MS))
VLog.LogFmtS1("%s\n", sys._argv[i + 1])
PrintUsage()
@@ -355,7 +355,7 @@ def ParseScanArgs() -> int:
Has_Timeout = 1
i += 1
else:
VLog.LogStr("错误: --need-confirm-timeout 需要一个毫秒数参数\n")
VLog.LogStr("Error: --need-confirm-timeout requires a millisecond argument\n")
PrintUsage()
return 1
elif string.strcmp(A, "--path") == 0:
@@ -364,23 +364,23 @@ def ParseScanArgs() -> int:
VDirs.AddOverridePath(sys._argv[i + 1])
i += 1
else:
VLog.LogStr("错误: --path 需要一个目录参数\n")
VLog.LogStr("Error: --path requires a directory argument\n")
return 1
else:
VLog.LogFmtS1("错误: 未知参数 %s\n", A)
VLog.LogFmtS1("Error: unknown argument %s\n", A)
PrintUsage()
return 1
i += 1
# --search-paths is a pure query switch (build table + print, no cache, no scan), so --cache
# is optional; other checks stay strict (a provided --cache is still accepted, just unused).
if Cache_Path is None and Search_Paths_Flag == 0:
VLog.LogStr("错误: scan 模式必须提供 --cache <path>\n")
VLog.LogStr("Error: scan mode requires --cache <path>\n")
PrintUsage()
return 1
# --need-confirm-timeout alone is not an error, only a warning: it is subordinate to
# --need-confirm, so erroring would fail needlessly on flag order; the warning avoids silence.
if Has_Timeout != 0 and Need_Confirm <= 0:
VLog.LogStr("警告: --need-confirm-timeout 未与 --need-confirm 一起使用,已忽略\n")
VLog.LogStr("Warning: --need-confirm-timeout is used without --need-confirm, ignored\n")
return 0
@@ -403,59 +403,59 @@ def ParseWatchArgs() -> int:
if i + 1 < sys._argc and sys._argv[i + 1] is not None:
S: int = ParseUint(sys._argv[i + 1], SECS_MAX)
if S <= 0:
VLog.LogStr("confirm-watch: --interval 需要一个 >=1 的整数字面量秒数\n")
VLog.LogStr("confirm-watch: --interval requires an integer-literal second value >= 1\n")
return 1
Watch_Interval = S
i += 1
else:
VLog.LogStr("confirm-watch: --interval 需要一个秒数参数\n")
VLog.LogStr("confirm-watch: --interval requires a second argument\n")
return 1
elif string.strcmp(A, "--timeout") == 0:
if i + 1 < sys._argc and sys._argv[i + 1] is not None:
T: int = ParseUint(sys._argv[i + 1], CONFIRM_TIMEOUT_MAX_MS)
if T < 0:
VLog.LogStr("confirm-watch: --timeout 需要一个非负整数字面量毫秒数\n")
VLog.LogStr("confirm-watch: --timeout requires a non-negative integer-literal millisecond value\n")
return 1
Watch_Timeout = T
i += 1
else:
VLog.LogStr("confirm-watch: --timeout 需要一个毫秒数参数\n")
VLog.LogStr("confirm-watch: --timeout requires a millisecond argument\n")
return 1
elif string.strcmp(A, "--parent-pid") == 0:
if i + 1 < sys._argc and sys._argv[i + 1] is not None:
P: LONGLONG = ParseUint(sys._argv[i + 1], PID_MAX)
if P <= 0:
VLog.LogStr("confirm-watch: --parent-pid 需要一个正整数\n")
VLog.LogStr("confirm-watch: --parent-pid requires a positive integer\n")
return 1
Watch_ParentPid = P
Has_Pid = 1
i += 1
else:
VLog.LogStr("confirm-watch: --parent-pid 需要一个 pid 参数\n")
VLog.LogStr("confirm-watch: --parent-pid requires a pid argument\n")
return 1
elif string.strcmp(A, "--ipc-write") == 0:
if i + 1 < sys._argc and sys._argv[i + 1] is not None:
if ParseUint64(sys._argv[i + 1]) == 0:
VLog.LogFmtS1("confirm-watch: --ipc-write 需要一个十进制非负整数字面量句柄值,实际为: ",
VLog.LogFmtS1("confirm-watch: --ipc-write requires a decimal non-negative integer-literal handle value, got: ",
sys._argv[i + 1])
VLog.LogStr("\n")
return 1
Watch_IpcWrite = U64_Val
i += 1
else:
VLog.LogStr("confirm-watch: --ipc-write 需要一个句柄数值参数\n")
VLog.LogStr("confirm-watch: --ipc-write requires a handle-value argument\n")
return 1
elif string.strcmp(A, "--verbose") == 0:
Watch_Verbose = 1 # valueless switch: appended by the parent when it is verbose
else:
VLog.LogFmtS1("confirm-watch: 未知参数 %s\n", A)
VLog.LogFmtS1("confirm-watch: unknown argument %s\n", A)
return 1
i += 1
if Watch_Interval <= 0:
VLog.LogStr("confirm-watch: 缺少 --interval <秒>\n")
VLog.LogStr("confirm-watch: missing --interval <sec>\n")
return 1
if Has_Pid == 0:
VLog.LogStr("confirm-watch: 缺少 --parent-pid <pid>\n")
VLog.LogStr("confirm-watch: missing --parent-pid <pid>\n")
return 1
if Watch_Timeout < 0:
Watch_Timeout = DEFAULT_CONFIRM_TIMEOUT_MS
+18 -18
View File
@@ -92,7 +92,7 @@ def AddOverridePath(Path: str) -> int:
if Path is None:
return 0
if Override_Count >= COMMON_DIR_CAPACITY:
VLog.LogFmtIS(" 警告: --path 数量已达上限 (%d),忽略: %s\n", COMMON_DIR_CAPACITY, Path)
VLog.LogFmtIS(" Warning: --path count reached limit (%d), ignored: %s\n", COMMON_DIR_CAPACITY, Path)
return 0
Override_Dir[Override_Count] = Path
Override_Count += 1
@@ -109,7 +109,7 @@ def AddCommonDirForce(Path: str) -> int:
if string.strcmp(Common_Directory[i], Path) == 0:
return 0
if Common_Dir_Count >= COMMON_DIR_CAPACITY:
VLog.LogFmt1(" 警告: 常用目录表已满 (%d),已忽略该条\n", sizet(COMMON_DIR_CAPACITY))
VLog.LogFmt1(" Warning: common dir table full (%d), entry ignored\n", sizet(COMMON_DIR_CAPACITY))
return 0
Common_Directory[Common_Dir_Count] = Path
Common_Dir_Count += 1
@@ -133,7 +133,7 @@ def AddFixedDriveProgramFiles() -> int:
SetErrorMode(SEM_FAILCRITICALERRORS)
Mask: DWORD = GetLogicalDrives()
if Mask == 0:
VLog.LogFmt1(" 警告: GetLogicalDrives 失败 (win32 err=%u)\n", wb.GetLastError())
VLog.LogFmt1(" Warning: GetLogicalDrives failed (win32 err=%u)\n", wb.GetLastError())
return 0
Added: int = 0
for Letter in range(26):
@@ -167,14 +167,14 @@ def AddDesktopFallback(Buf: bytes) -> int:
n: DWORD = GetEnvironmentVariableA("USERPROFILE", Buf, VFs.PATH_BUF_CAP)
# Also append "\Desktop" (8 bytes) + NUL, so n + 9 must not exceed the buffer capacity
if n == 0 or n + uint32(DESKTOP_SUFFIX_CAP) > uint32(VFs.PATH_BUF_CAP):
VLog.LogFmt1(" 桌面兜底: 获取 USERPROFILE 失败或过长 (win32 err=%u)\n", wb.GetLastError())
VLog.LogFmt1(" desktop fallback: USERPROFILE missing or too long (win32 err=%u)\n", wb.GetLastError())
return 0
Buf[n] = 0
string.strcat(Buf, r"\Desktop")
VLog.VerboseFmtS1(" 桌面兜底: 使用 %s\n", str(Buf))
VLog.VerboseFmtS1(" desktop fallback: using %s\n", str(Buf))
if AddCommonDir(str(Buf)) != 0:
return 1
VLog.LogStr(" 警告: 桌面兜底路径不存在或非目录,未加入常用目录表\n")
VLog.LogStr(" Warning: desktop fallback path missing or not a directory, not added to common dir table\n")
return 0
@@ -183,14 +183,14 @@ def AddDesktopFallback(Buf: bytes) -> int:
def AddCurrentUserDesktop() -> int:
Buf: bytes = _mbuddy.alloc(VFs.PATH_BUF_CAP)
if Buf is None:
VLog.LogStr(" 警告: 桌面路径缓冲区分配失败\n")
VLog.LogStr(" Warning: desktop path buffer allocation failed\n")
return 0
# FOLDERID_Desktop {B4BFCC3A-DB2C-424C-B029-7FE99A87C641} as 16 raw LE bytes:
# 3A CC BF B4 2C DB 4C 42 B0 29 7F E9 9A 87 C6 41 (wb.GUID mis-models Data4 as a pointer).
GuidBuf: BYTEPTR = stdlib.malloc(KNOWNFOLDERID_SIZE)
if GuidBuf is None:
VLog.LogStr(" 警告: KNOWNFOLDERID 缓冲分配失败,改用兜底路径\n")
VLog.LogStr(" Warning: KNOWNFOLDERID buffer allocation failed, using fallback path\n")
return AddDesktopFallback(Buf)
GuidBuf[0] = 0x3A
GuidBuf[1] = 0xCC
@@ -216,39 +216,39 @@ def AddCurrentUserDesktop() -> int:
# On failure *ppszPath is NULL by spec; still free a non-NULL return defensively to avoid leaks
if Pwsz is not None:
CoTaskMemFree(t.CPtr(Pwsz))
VLog.LogFmt1(" 桌面: SHGetKnownFolderPath 失败 (hr=0x%X),改用兜底路径\n", sizet(uint32(Hr)))
VLog.LogFmt1(" desktop: SHGetKnownFolderPath failed (hr=0x%X), using fallback path\n", sizet(uint32(Hr)))
return AddDesktopFallback(Buf)
if Pwsz is None:
VLog.LogStr(" 桌面: SHGetKnownFolderPath 返回空指针,改用兜底路径\n")
VLog.LogStr(" desktop: SHGetKnownFolderPath returned null pointer, using fallback path\n")
return AddDesktopFallback(Buf)
# UTF-16 -> UTF-8: query needed bytes first (incl. NUL; Needed<=0 means failure)
Needed: int = VFs.WideToUtf8Needed(Pwsz)
if Needed <= 0 or Needed > VFs.PATH_BUF_CAP:
CoTaskMemFree(t.CPtr(Pwsz))
VLog.LogFmt2(" 桌面: 路径为空或过长 (%d 字节, 上限 %d),改用兜底路径\n",
VLog.LogFmt2(" desktop: path empty or too long (%d bytes, limit %d), using fallback path\n",
sizet(uint32(Needed)), sizet(VFs.PATH_BUF_CAP))
return AddDesktopFallback(Buf)
Got: int = VFs.WideToUtf8(Pwsz, Buf, VFs.PATH_BUF_CAP)
CoTaskMemFree(t.CPtr(Pwsz)) # Free immediately after use: single free point for both branches
if Got == 0:
VLog.LogFmt1(" 桌面: UTF-16→UTF-8 转换失败 (win32 err=%u),改用兜底路径\n", wb.GetLastError())
VLog.LogFmt1(" desktop: UTF-16 to UTF-8 conversion failed (win32 err=%u), using fallback path\n", wb.GetLastError())
return AddDesktopFallback(Buf)
if AddCommonDir(str(Buf)) != 0:
return 1
VLog.LogStr(" 警告: 桌面路径不存在或非目录,未加入常用目录表\n")
VLog.LogStr(" Warning: desktop path missing or not a directory, not added to common dir table\n")
return 0
# Print the common dir table (verbose only). Returns 0=non-empty, 1=empty
def PrintCommonDirs() -> int:
if Common_Dir_Count <= 0:
VLog.VerboseStr(" (表为空)\n")
VLog.VerboseStr(" (table empty)\n")
return 1
for i in range(Common_Dir_Count):
VLog.VerboseFmtIS(" [%d] %s\n", sizet(i), Common_Directory[i])
VLog.VerboseFmt1(" 合计 %d 个常用目录\n", sizet(Common_Dir_Count))
VLog.VerboseFmt1(" total %d common dirs\n", sizet(Common_Dir_Count))
return 0
@@ -260,13 +260,13 @@ def BuildTable() -> int:
AddCommonDirForce(r"D:\Viper")
AddFixedDriveProgramFiles() # Per-drive Program Files / Program Files (x86)
AddCurrentUserDesktop() # Current user desktop
VLog.VerboseFmt1(" 目录表来源: 自动表 (%u 条)\n", sizet(Common_Dir_Count))
VLog.VerboseFmt1(" dir table source: auto table (%u entries)\n", sizet(Common_Dir_Count))
else:
# Force replace: no auto-build; table = --path order (de-dup handled by AddCommonDirForce)
for i in range(Override_Count):
if VFs.PathIsDir(Override_Dir[i]) == 0:
VLog.LogFmtS1(" 警告: --path 不存在或非目录,跳过: %s\n", Override_Dir[i])
VLog.LogFmtS1(" Warning: --path missing or not a directory, skipped: %s\n", Override_Dir[i])
else:
AddCommonDirForce(Override_Dir[i])
VLog.VerboseFmt1(" 目录表来源: 覆盖表 (%u 条)\n", sizet(Common_Dir_Count))
VLog.VerboseFmt1(" dir table source: override table (%u entries)\n", sizet(Common_Dir_Count))
return PrintCommonDirs()
+36 -36
View File
@@ -310,7 +310,7 @@ def GuardIpcCreate() -> wb.HANDLE:
hWrite: wb.HANDLE = None
if wf.CreatePipe(&hRead, &hWrite,
&Sa, ULONG(IPC_PIPE_SIZE)) == 0:
VLog.LogFmt1("[confirm] 警告: check 通道 CreatePipe 失败 (GetLastError=%u)\n",
VLog.LogFmt1("[confirm] Warning: check channel CreatePipe failed (GetLastError=%u)\n",
sizet(wb.GetLastError()))
return None
# read end is parent-only: clear inherit bit so no child (watchdog, --version probe) inherits it
@@ -405,12 +405,12 @@ def SpawnWatchdog(IntervalSec: int, TimeoutMs: int) -> int:
_mbuddy.free(t.CPtr(Self))
if Cmd is not None:
_mbuddy.free(t.CPtr(Cmd))
VLog.LogStr("[confirm] 警告: 看门狗缓冲分配失败,未启动\n")
VLog.LogStr("[confirm] Warning: watchdog buffer allocation failed, not started\n")
return 0
if SelfExePath(Self, SELF_PATH_CAP) == 0:
_mbuddy.free(t.CPtr(Self))
_mbuddy.free(t.CPtr(Cmd))
VLog.LogStr("[confirm] 警告: GetModuleFileNameA 取自身路径失败,看门狗未启动\n")
VLog.LogStr("[confirm] Warning: GetModuleFileNameA failed to get own path, watchdog not started\n")
return 0
# Build the pipe first (the write-end value must be in the command line before CreateProcessA).
@@ -419,7 +419,7 @@ def SpawnWatchdog(IntervalSec: int, TimeoutMs: int) -> int:
if IpcW is None:
_mbuddy.free(t.CPtr(Self))
_mbuddy.free(t.CPtr(Cmd))
VLog.LogStr("[confirm] 警告: check 通道管道建立失败,看门狗未启动\n")
VLog.LogStr("[confirm] Warning: check channel pipe creation failed, watchdog not started\n")
return 0
# First token is the self path (quoted for spaces); --verbose is forwarded so the child uses the
@@ -444,7 +444,7 @@ def SpawnWatchdog(IntervalSec: int, TimeoutMs: int) -> int:
GuardIpcClose()
_mbuddy.free(t.CPtr(Self))
_mbuddy.free(t.CPtr(Cmd))
VLog.LogStr("[confirm] 警告: stdin 句柄无效,看门狗未启动(心跳确认需要 stdin)\n")
VLog.LogStr("[confirm] Warning: stdin handle invalid, watchdog not started (heartbeat confirmation requires stdin)\n")
return 0
# Best-effort mark standard handles inheritable (console handles usually inherit anyway; ignore)
wf.SetHandleInformation(hIn, wf.HANDLE_FLAG_INHERIT, wf.HANDLE_FLAG_INHERIT)
@@ -469,7 +469,7 @@ def SpawnWatchdog(IntervalSec: int, TimeoutMs: int) -> int:
if Ok == 0:
wb.CloseHandle(IpcW)
GuardIpcClose()
VLog.LogFmt1("[confirm] 警告: 看门狗 CreateProcessA 失败 (GetLastError=%u),未启动\n",
VLog.LogFmt1("[confirm] Warning: watchdog CreateProcessA failed (GetLastError=%u), not started\n",
sizet(CreateErr))
return 0
@@ -485,7 +485,7 @@ def SpawnWatchdog(IntervalSec: int, TimeoutMs: int) -> int:
if Wp == wb.WAIT_OBJECT_0:
Ec: ULONG = 0
wp.GetExitCodeProcess(Watch_Process, &Ec)
VLog.LogFmt1("[confirm] 看门狗拉起后立即退出 (exit=%u),心跳确认无法生效\n",
VLog.LogFmt1("[confirm] watchdog exited immediately after spawn (exit=%u), heartbeat confirmation cannot work\n",
sizet(Ec))
# Like StopWatchdog, close each handle once and clear the module handles (avoid a later
# StopWatchdog / re-Spawn double-closing).
@@ -498,9 +498,9 @@ def SpawnWatchdog(IntervalSec: int, TimeoutMs: int) -> int:
# the same msg because consumers abort on "exit"==1; the precise reason goes to stderr above.
VLog.StdoutWrite("{\"exit\":1, \"msg\": \"timeout/ok!=1\"}\n")
return 0
VLog.LogFmt2("[confirm] 心跳看门狗已启动 (pid=%u, 每 %u 秒一次",
VLog.LogFmt2("[confirm] heartbeat watchdog started (pid=%u, every %u seconds",
sizet(Pi.dwProcessId), sizet(IntervalSec))
VLog.LogFmt1(", 确认超时 %u ms)\n", sizet(TimeoutMs))
VLog.LogFmt1(", confirmation timeout %u ms)\n", sizet(TimeoutMs))
return 1
@@ -599,16 +599,16 @@ def GuardClassifyLine(Buf: bytes, Len: int) -> int:
# stderr-only and does not change heartbeat semantics.
def GuardForwardLine():
if Guard_IpcWrite is None or Guard_IpcWrite == wb.INVALID_HANDLE_VALUE:
VLog.LogStr("[confirm] 警告: 收到 check 请求但无 check 通道(未传 --ipc-write),已丢弃该行\n")
VLog.LogStr("[confirm] Warning: check request received but no check channel (--ipc-write not passed), line dropped\n")
return
bw: ULONG = 0
if Guard_Fill > 0:
if wf.WriteFile(Guard_IpcWrite, Guard_Line, ULONG(Guard_Fill),
&bw, None) == 0:
VLog.LogStr("[confirm] 警告: check 请求转发失败(写管道失败),已丢弃该行\n")
VLog.LogStr("[confirm] Warning: check request forward failed (pipe write failed), line dropped\n")
return
if wf.WriteFile(Guard_IpcWrite, "\n", 1, &bw, None) == 0:
VLog.LogStr("[confirm] 警告: check 请求转发失败(写行分隔符失败),该行可能不完整\n")
VLog.LogStr("[confirm] Warning: check request forward failed (line separator write failed), the line may be incomplete\n")
# ============================================================
@@ -673,7 +673,7 @@ def GuardFeedPending() -> int:
def GuardPendPut(Ch: int):
global Guard_PendLen
if Guard_PendLen >= GUARD_PEND_CAP:
VLog.LogFmt1("[confirm] 警告: 未消费输入 FIFO 已满(容量 %u),丢弃该批字节\n",
VLog.LogFmt1("[confirm] Warning: unconsumed-input FIFO full (capacity %u), dropping this byte batch\n",
sizet(GUARD_PEND_CAP))
return
Guard_Pend[Guard_PendLen] = char(Ch)
@@ -767,7 +767,7 @@ def GuardEnterRaw(hIn: wb.HANDLE) -> int:
if SetConsoleCtrlHandler(GuardCtrlHandler, GUARD_CTRL_ADD) != 0:
Guard_CtrlHandlerOn = 1
else:
VLog.LogStr("[confirm] 警告: SetConsoleCtrlHandler 注册失败,Ctrl+C 时该终端可能残留已清行输入的模式\n")
VLog.LogStr("[confirm] Warning: SetConsoleCtrlHandler registration failed; on Ctrl+C this console may keep the mode with line input cleared\n")
if wc.SetConsoleMode(hIn, New) == 0:
# Clear Guard_RawActive before Guard_SavedMode: the handler writes back only when
# RawActive != 0, so it never pushes the zeroed SavedMode to the console.
@@ -887,7 +887,7 @@ def GuardPumpStream(hIn: wb.HANDLE, Kind: int) -> int:
# TerminateProcess(parent) -> sys.exit(1). The parent is killed with no cleanup (no cache, no JSON).
def GuardWatchFail(hParent: wb.HANDLE, Reason: str):
if Reason is not None:
VLog.LogFmtS1("[confirm] 失败: %s\n", Reason)
VLog.LogFmtS1("[confirm] Failure: %s\n", Reason)
VLog.StdoutWrite("{\"exit\":1, \"msg\": \"timeout/ok!=1\"}\n")
if hParent is not None and hParent != wb.INVALID_HANDLE_VALUE:
wp.TerminateProcess(hParent, 1)
@@ -926,7 +926,7 @@ def GuardWaitConfirm(hIn: wb.HANDLE, hParent: wb.HANDLE, Kind: int, TimeoutMs: i
# Clear ENABLE_LINE_INPUT so Enter arrives as a KEY_EVENT we can judge ourselves (see module
# head "Console mode tradeoff"). If the mode cannot be read/switched, fail as usual.
if GuardEnterRaw(hIn) == 0:
GuardWatchFail(hParent, "控制台模式读取/切换失败")
GuardWatchFail(hParent, "console mode read/switch failed")
return 0
Deadline: ULONGLONG = wb.GetTickCount64() + ULONGLONG(uint32(TimeoutMs))
while True:
@@ -949,13 +949,13 @@ def GuardWaitConfirm(hIn: wb.HANDLE, hParent: wb.HANDLE, Kind: int, TimeoutMs: i
if Cls == GUARD_LINE_BADOK:
# valid shape but bad value => fatal failure with the same stderr text
GuardLeaveRaw(hIn)
VLog.LogFmtS1("[confirm] 收到非 {\"ok\": 1} 的输入: %s\n", str(Guard_Line))
VLog.LogFmtS1("[confirm] received input that is not {\"ok\": 1}: %s\n", str(Guard_Line))
GuardWatchFail(hParent, "ok != 1")
return 0
# suspect shape (first char not '{' / no '}' / bad length, incl. empty) => echo (truncated) and ignore
if Guard_Fill > GUARD_SUSPECT_ECHO_MAX:
Guard_Line[GUARD_SUSPECT_ECHO_MAX] = 0 # echo only the prefix (the line is then dropped)
VLog.LogFmtS1("[confirm] 忽略可疑输入行: %s\n", str(Guard_Line))
VLog.LogFmtS1("[confirm] ignoring suspect input line: %s\n", str(Guard_Line))
Guard_Fill = 0
Guard_Line[0] = 0
# Both ignore paths fall through to the deadline check + wait below. Ignoring does not
@@ -964,15 +964,15 @@ def GuardWaitConfirm(hIn: wb.HANDLE, hParent: wb.HANDLE, Kind: int, TimeoutMs: i
if R < 0:
GuardLeaveRaw(hIn)
if R == GUARD_PUMP_EOF:
GuardWatchFail(hParent, "stdin 已到 EOF,不可能再有输入")
GuardWatchFail(hParent, "stdin reached EOF, no more input possible")
else:
GuardWatchFail(hParent, "读取 stdin 失败")
GuardWatchFail(hParent, "reading stdin failed")
return 0
# (3) nothing read: wait for stdin readable / parent exit / deadline, cap min(remaining, GUARD_POLL_MS)
Rem2: ULONG = GuardRemainMs(Deadline)
if Rem2 == 0:
GuardLeaveRaw(hIn)
GuardWatchFail(hParent, "超时无输入")
GuardWatchFail(hParent, "timeout with no input")
return 0
Sl: ULONG = Rem2
if Sl > ULONG(GUARD_POLL_MS):
@@ -987,7 +987,7 @@ def GuardWaitConfirm(hIn: wb.HANDLE, hParent: wb.HANDLE, Kind: int, TimeoutMs: i
return 0
if Wm == ULONG(wb.WAIT_FAILED):
GuardLeaveRaw(hIn)
GuardWatchFail(hParent, "等待输入失败")
GuardWatchFail(hParent, "waiting for input failed")
return 0
# other results (stdin signaled / WAIT_TIMEOUT) => loop back and pump again
GuardLeaveRaw(hIn)
@@ -1007,23 +1007,23 @@ def RunConfirmWatch() -> int:
ParentPid: LONGLONG = VCli.WatchParentPid()
IntervalMs: ULONG = ULONG(IntervalSec) * ULONG(1000)
if GuardWatchInit() == 0:
VLog.LogStr("[confirm] 看门狗: 行/记录/时间戳/未消费输入缓冲分配失败\n")
VLog.LogStr("[confirm] watchdog: line/record/timestamp/unconsumed-input buffer allocation failed\n")
return 1
VLog.LogFmt3("[confirm] 看门狗启动: 每 %u 秒要求一次 {\"ok\": 1},超时 %u ms,父进程 pid=%u\n",
VLog.LogFmt3("[confirm] watchdog started: requires {\"ok\": 1} every %u seconds, timeout %u ms, parent pid=%u\n",
sizet(IntervalSec), sizet(TimeoutMs), sizet(ParentPid))
# Parent handle: used both for parent-exit detection (SYNCHRONIZE) and to terminate the parent
hParent: wb.HANDLE = wp.OpenProcess(ULONG(WATCH_PARENT_ACCESS), 0, ULONG(ParentPid))
if hParent is None or hParent == wb.INVALID_HANDLE_VALUE:
# No parent handle => cannot monitor or terminate; log to stderr and exit 1
VLog.LogFmt1("[confirm] 看门狗: OpenProcess 父进程失败 (GetLastError=%u),自身退出\n",
VLog.LogFmt1("[confirm] watchdog: OpenProcess on parent failed (GetLastError=%u), exiting\n",
sizet(wb.GetLastError()))
return 1
hIn: wb.HANDLE = wf.GetStdHandle(wf.STD_INPUT_HANDLE)
if hIn is None or hIn == wb.INVALID_HANDLE_VALUE:
GuardWatchFail(hParent, "stdin 句柄无效")
GuardWatchFail(hParent, "stdin handle invalid")
return 1
# check channel write end: the handle value passed by the parent via `--ipc-write <value>`,
@@ -1035,42 +1035,42 @@ def RunConfirmWatch() -> int:
Ch0: int = 0
if Guard_IpcWrite is not None:
Ch0 = 1
VLog.VerboseFmt1("[confirm] 看门狗: check 通道=%u (1=已配置 0=无)\n", sizet(Ch0))
VLog.VerboseFmt1("[confirm] watchdog: check channel=%u (1=configured 0=none)\n", sizet(Ch0))
# Input kind is classified once (the handle never changes); console and pipe/redirect pump differently.
Kind: int = GuardInputKind(hIn)
VLog.VerboseFmt1("[confirm] 看门狗: stdin 形态=%u (1=控制台 2=管道 3=其它流)\n",
VLog.VerboseFmt1("[confirm] watchdog: stdin kind=%u (1=console 2=pipe 3=other stream)\n",
sizet(Kind))
# Console pump path (Hardening 1): primary ReadConsoleInputExA+NOWAIT, fallback query+ReadConsoleInputA.
# Resolve once here (later rounds reuse the cached state) and log the chosen path under --verbose.
if Kind == GUARD_IN_CONSOLE:
GuardResolveReadEx()
VLog.VerboseFmt1("[confirm] 看门狗: 控制台取数路径=%u (1=ReadConsoleInputExA+NOWAIT 2=降级查询+ReadConsoleInputA)\n",
VLog.VerboseFmt1("[confirm] watchdog: console read path=%u (1=ReadConsoleInputExA+NOWAIT 2=fallback query+ReadConsoleInputA)\n",
sizet(Guard_ReadExState))
while True:
# Phase 1: wait interval; if the parent exited first, exit immediately (no orphans)
Ws: ULONG = wsync.WaitForSingleObject(hParent, IntervalMs)
if Ws == wb.WAIT_OBJECT_0:
VLog.LogStr("[confirm] 看门狗: 父进程已退出,自身退出\n")
VLog.LogStr("[confirm] watchdog: parent exited, exiting\n")
wb.CloseHandle(hParent)
return 0
if Ws != wb.WAIT_TIMEOUT:
VLog.LogFmt1("[confirm] 看门狗: 等待父进程失败 (wait=%u)\n", sizet(Ws))
GuardWatchFail(hParent, "等待父进程失败")
VLog.LogFmt1("[confirm] watchdog: waiting for parent failed (wait=%u)\n", sizet(Ws))
GuardWatchFail(hParent, "waiting for parent failed")
return 1
# Phase 2: stderr prompt (deadline starts here) + non-blocking wait for a complete classified line
VLog.LogFmt2("[confirm] 请在 %u ms 内输入 {\"ok\": 1}(每 %u 秒一次)\n",
VLog.LogFmt2("[confirm] please enter {\"ok\": 1} within %u ms (every %u seconds)\n",
sizet(TimeoutMs), sizet(IntervalSec))
VLog.TimeStampNow(Guard_Stamp, GUARD_STAMP_CAP)
VLog.TimeStampAfter(Guard_Dl, GUARD_STAMP_CAP, sizet(TimeoutMs))
VLog.LogFmtS2("[confirm] 当前 %s, 本轮期限 %s\n", str(Guard_Stamp), str(Guard_Dl))
VLog.LogFmtS2("[confirm] now %s, this-round deadline %s\n", str(Guard_Stamp), str(Guard_Dl))
# Failure paths (deadline / bad value / read error / EOF) already finalized the process inside
# GuardWaitConfirm; a 0 return only means "parent exited", so the watchdog exits itself.
if GuardWaitConfirm(hIn, hParent, Kind, TimeoutMs) == 0:
VLog.LogStr("[confirm] 看门狗: 父进程已退出,自身退出\n")
VLog.LogStr("[confirm] watchdog: parent exited, exiting\n")
wb.CloseHandle(hParent)
return 0
VLog.LogFmt1("[confirm] 确认成功 (ok=1),下一次约 %u 秒后\n", sizet(IntervalSec))
VLog.LogFmt1("[confirm] confirmed (ok=1), next in about %u seconds\n", sizet(IntervalSec))
+8 -8
View File
@@ -95,12 +95,12 @@ def BuildEntry(Idx: int) -> str:
ValNeed = ValNeed + string.strlen(VCache.CacheSrc(Idx)) # source ("workspace"/"check")
Need: sizet = sizet(JSON_ENTRY_POOL_FIXED) + ValNeed
if Json_Pool.AllocOk(Need) == 0:
VLog.VerboseFmtIS(" 警告: JSON 工作池不足(本条需 %zu 字节),已跳过: %s\n",
VLog.VerboseFmtIS(" Warning: JSON pool insufficient (this entry needs %zu bytes), skipped: %s\n",
Need, VCache.CacheExe(Idx))
return None
Obj: json.JsonValue* = json.object(Json_Pool)
if Obj == None:
VLog.VerboseStr(" 警告: JSON 对象分配失败(工作池不足),已跳过该条目\n")
VLog.VerboseStr(" Warning: JSON object allocation failed (pool insufficient), entry skipped\n")
return None
# Key insertion order = output order. Convert executable/displayName to UTF-8 and hand to the
# library at once: Conv_Buf is reused, so a later copy would overwrite the executable string.
@@ -130,19 +130,19 @@ def BuildEntry(Idx: int) -> str:
N += 1
cu = cu.next
if Ok == 0 or N != 6:
VLog.VerboseStr(" 警告: JSON 条目构造不完整(工作池不足),已跳过该条目\n")
VLog.VerboseStr(" Warning: JSON entry construction incomplete (pool insufficient), entry skipped\n")
return None
# Capacity guard: the library's write default cap=4096 returns None on overflow; guard early
# to avoid a dropped entry. Output <= 2*Sum + SLACK(512) <= JSON_ENTRY_MAX.
if 2 * Sum + sizet(JSON_ENTRY_SLACK) > sizet(JSON_ENTRY_MAX):
VLog.VerboseFmtIS(" 警告: JSON 条目过长(超过 %zu 字节),已跳过: %s\n",
VLog.VerboseFmtIS(" Warning: JSON entry too long (exceeds %zu bytes), skipped: %s\n",
sizet(JSON_ENTRY_MAX), VCache.CacheExe(Idx))
return None
Txt: str = json.write(Json_Pool, Obj, False)
if Txt is None:
VLog.VerboseStr(" 警告: JSON 序列化失败(工作池不足),已跳过该条目\n")
VLog.VerboseStr(" Warning: JSON serialization failed (pool insufficient), entry skipped\n")
return None
return Txt
@@ -152,7 +152,7 @@ def EmitJson() -> int:
JReset()
if Json_Pool is None:
# invariant: EmitJson is reached only after JsonInit succeeds; this branch is only to avoid a crash
VLog.LogStr(" 警告: JSON 工作池未初始化,跳过本轮 JSON 输出\n")
VLog.LogStr(" Warning: JSON pool not initialized, skipping JSON output this round\n")
return 0
Truncated: int = 0
Skipped: int = 0
@@ -183,9 +183,9 @@ def EmitJson() -> int:
if JWrite("\n") == 0:
Truncated = 1
if Truncated != 0:
VLog.LogFmt2(" 警告: JSON 输出缓冲不足,本轮已截断(已写 %zu 条 / 缓存 %zu 条)\n",
VLog.LogFmt2(" Warning: JSON output buffer insufficient, round truncated (wrote %zu entries / cache %zu entries)\n",
sizet(Count), sizet(VCache.CacheCount()))
if Skipped != 0:
VLog.LogFmt1(" 警告: 本轮有 %zu 条 JSON 条目因过长/工作池不足被跳过\n",
VLog.LogFmt1(" Warning: %zu JSON entries skipped this round due to excessive length / insufficient pool\n",
sizet(Skipped))
return 0
+26 -26
View File
@@ -294,19 +294,19 @@ def Phase1VersionSection(Target: str) -> int:
if buf is None:
rc_err: int = VFs.LastReadErr
if rc_err == 1:
VLog.LogFmt1(" 打开文件失败 (win32 err=%u)\n", sizet(VFs.LastReadWinErr))
VLog.LogFmt1(" open file failed (win32 err=%u)\n", sizet(VFs.LastReadWinErr))
elif rc_err == 2:
VLog.LogStr(" 读取文件大小失败\n")
VLog.LogStr(" read file size failed\n")
elif rc_err == 3:
VLog.LogStr(" 文件为空\n")
VLog.LogStr(" file is empty\n")
else:
VLog.LogStr(" 分配缓冲区失败\n")
VLog.LogStr(" buffer allocation failed\n")
return 1
total: sizet = VFs.LastReadSize
Pe_DbgGot = total
if total < 0x40:
VLog.LogStr(" 不是有效的 PE 文件(文件过短)\n")
VLog.LogStr(" not a valid PE file (file too short)\n")
PeDbgPrint(1, total, uint32(0), uint32(0))
stdlib.free(t.CPtr(buf))
return 1
@@ -315,7 +315,7 @@ def Phase1VersionSection(Target: str) -> int:
# DOS header
if ReadU16(ub, 0) != DOS_SIGNATURE:
VLog.LogStr(" 不是有效的 PE 文件(缺少 MZ 头)\n")
VLog.LogStr(" not a valid PE file (missing MZ header)\n")
PeDbgPrint(2, total, uint32(0), uint32(0))
stdlib.free(t.CPtr(buf))
return 1
@@ -324,12 +324,12 @@ def Phase1VersionSection(Target: str) -> int:
lfanew: uint32 = ReadU32(ub, E_LFANEW_OFF)
Pe_DbgLfanew = lfanew
if sizet(lfanew) + 24 > total:
VLog.LogStr(" 不是有效的 PE 文件(e_lfanew 越界)\n")
VLog.LogStr(" not a valid PE file (e_lfanew out of range)\n")
PeDbgPrint(3, total, uint32(0), uint32(0))
stdlib.free(t.CPtr(buf))
return 1
if ReadU32(ub, int(lfanew)) != NT_SIGNATURE:
VLog.LogStr(" 不是有效的 PE 文件(缺少 PE 签名)\n")
VLog.LogStr(" not a valid PE file (missing PE signature)\n")
PeDbgPrint(4, total, uint32(0), uint32(0))
stdlib.free(t.CPtr(buf))
return 1
@@ -340,7 +340,7 @@ def Phase1VersionSection(Target: str) -> int:
Pe_DbgOptSize = optsize
magic: uint32 = ReadU16(ub, int(lfanew) + OPT_MAGIC_OFF)
if magic != OPT_MAGIC_PE32 and magic != OPT_MAGIC_PE32P:
VLog.LogFmt1(" 可选头 Magic 未知: 0x%X\n", magic)
VLog.LogFmt1(" unknown optional-header Magic: 0x%X\n", magic)
PeDbgPrint(5, total, uint32(0), uint32(0))
stdlib.free(t.CPtr(buf))
return 1
@@ -348,7 +348,7 @@ def Phase1VersionSection(Target: str) -> int:
sec_off: int = int(lfanew) + 24 + int(optsize)
Pe_DbgSecOff = uint32(sec_off)
if sizet(sec_off) + sizet(nsec) * SEC_ENTRY_SIZE > total:
VLog.LogFmt1(" 节表越界(节数=%u)\n", nsec)
VLog.LogFmt1(" section table out of range (section count=%u)\n", nsec)
PeDbgPrint(6, total, uint32(0), uint32(0))
stdlib.free(t.CPtr(buf))
return 1
@@ -366,9 +366,9 @@ def Phase1VersionSection(Target: str) -> int:
break
if found == 0:
VLog.LogFmtS1(" 未找到 %s 节\n", VERSION_SECTION)
VLog.LogFmtS1(" section %s not found\n", VERSION_SECTION)
PeDbgPrint(7, total, uint32(0), uint32(0))
VLog.LogStr(" 实际节名:\n")
VLog.LogStr(" actual section names:\n")
name8: bytes = stdlib.malloc(9)
if name8 is not None:
for j in range(int(nsec)):
@@ -380,7 +380,7 @@ def Phase1VersionSection(Target: str) -> int:
# up to the first NUL
if sizet(raw_ptr) + sizet(raw_size) > total:
VLog.LogFmt2(" 段内容越界 (offset=%u size=%u)\n", raw_ptr, raw_size)
VLog.LogFmt2(" section content out of range (offset=%u size=%u)\n", raw_ptr, raw_size)
PeDbgPrint(6, total, raw_ptr, raw_size)
stdlib.free(t.CPtr(buf))
return 1
@@ -388,8 +388,8 @@ def Phase1VersionSection(Target: str) -> int:
while vlen < raw_size and ub[int(raw_ptr) + int(vlen)] != 0:
vlen += 1
VLog.LogFmt1(" 原始长度: %u 字节\n", vlen)
VLog.LogFmtS1(" 版本串: %s\n", str(buf + int(raw_ptr)))
VLog.LogFmt1(" raw length: %u bytes\n", vlen)
VLog.LogFmtS1(" version string: %s\n", str(buf + int(raw_ptr)))
stdlib.free(t.CPtr(buf))
return 0
@@ -397,33 +397,33 @@ def Phase1VersionSection(Target: str) -> int:
# ver subcommand: [0/3] -> [1/3] -> [2/3] checks and exit codes (logs to stderr)
def RunVer() -> int:
if sys._argc < 3 or sys._argv[2] is None:
VLog.LogStr("用法: VET.exe ver <目标 exe 路径>\n")
VLog.LogStr("Usage: VET.exe ver <target exe path>\n")
return 1
target: str = sys._argv[2]
# banner (no path) is kept; the target path is process detail, --verbose only
VLog.LogFmtS1("VET - Viper Environment Tool %s\n\n", VCli.Version())
VLog.VerboseFmtS1("目标: %s\n\n", target)
VLog.VerboseFmtS1("target: %s\n\n", target)
VLog.LogStr("[0/3] 常用目录表\n")
VLog.LogStr("[0/3] common directory table\n")
rc0: int = VDirs.BuildTable()
if rc0 == 0:
VLog.LogStr(" ✓ 通过\n\n")
VLog.LogStr(" ✓ pass\n\n")
else:
VLog.LogStr(" ✗ 失败(表为空)\n\n")
VLog.LogStr(" ✗ fail (table empty)\n\n")
VLog.LogFmtS1("[1/3] 内嵌版本段 (%s)\n", VERSION_SECTION)
VLog.LogFmtS1("[1/3] embedded version section (%s)\n", VERSION_SECTION)
rc1: int = Phase1VersionSection(target)
if rc1 == 0:
VLog.LogStr(" ✓ 通过\n\n")
VLog.LogStr(" ✓ pass\n\n")
else:
VLog.LogStr(" ✗ 失败\n\n")
VLog.LogStr(" ✗ fail\n\n")
VLog.LogStr("[2/3] --version 前缀\n")
VLog.LogStr("[2/3] --version prefix\n")
rc2: int = VProc.RunVersionPrefixCheck(target)
if rc2 == 0:
VLog.LogStr(" ✓ 通过\n")
VLog.LogStr(" ✓ pass\n")
else:
VLog.LogStr(" ✗ 失败\n")
VLog.LogStr(" ✗ fail\n")
if rc0 == 0 and rc1 == 0 and rc2 == 0:
return 0
+22 -22
View File
@@ -94,23 +94,23 @@ def RunVersionProbe(Target: str, Out: bytes, OutCap: sizet, Trace: int) -> int:
if Target is None or Out is None or OutCap < 2:
Probe_LaunchStage = 1
if Trace != 0:
VLog.VerboseStr(" [--version] 启动失败 (rc=1): 参数非法\n")
VLog.VerboseStr(" [--version] launch failed (rc=1): invalid arguments\n")
return 1
if string.strlen(Target) > sizet(PROBE_TARGET_MAX):
Probe_LaunchStage = 1
VLog.LogStr(" 警告: --version 目标路径过长 (rc=1),已跳过该候选\n")
VLog.LogStr(" Warning: --version target path too long (rc=1), candidate skipped\n")
return 1
T0: ULONGLONG = wb.GetTickCount64()
if Trace != 0:
VLog.VerboseFmtS1(" [--version] 开始: %s\n", Target)
VLog.VerboseFmtS1(" [--version] start: %s\n", Target)
# --- command buffer: "<exe>" --version ---
n: sizet = string.strlen(Target) + sizet(PROBE_CMD_EXTRA_CAP)
CmdLine: bytes = stdlib.malloc(n)
if CmdLine is None:
Probe_LaunchStage = 2
VLog.LogStr(" 警告: --version 探测启动失败 (rc=1, 命令缓冲分配失败)\n")
VLog.LogStr(" Warning: --version probe launch failed (rc=1, command buffer allocation failed)\n")
return 1
viperlib.snprintf(CmdLine, n, "\"%s\" --version", Target)
@@ -130,7 +130,7 @@ def RunVersionProbe(Target: str, Out: bytes, OutCap: sizet, Trace: int) -> int:
Probe_LaunchStage = 3
Probe_LaunchErr = PipeErr
stdlib.free(t.CPtr(CmdLine))
VLog.LogStr(" 警告: --version 探测启动失败 (rc=1, CreatePipe)\n")
VLog.LogStr(" Warning: --version probe launch failed (rc=1, CreatePipe)\n")
return 1
# The read end is not inherited (write-end inheritance is set by bInheritHandle)
wf.SetHandleInformation(hRead, wf.HANDLE_FLAG_INHERIT, 0)
@@ -156,7 +156,7 @@ def RunVersionProbe(Target: str, Out: bytes, OutCap: sizet, Trace: int) -> int:
Probe_LaunchErr = CreateErr
wb.CloseHandle(hRead)
wb.CloseHandle(hWrite)
VLog.LogFmt1(" 警告: --version 探测启动失败 (rc=1, CreateProcessA, GetLastError=%u)\n",
VLog.LogFmt1(" Warning: --version probe launch failed (rc=1, CreateProcessA, GetLastError=%u)\n",
sizet(CreateErr))
return 1
@@ -199,28 +199,28 @@ def RunVersionProbe(Target: str, Out: bytes, OutCap: sizet, Trace: int) -> int:
if TimedOut != 0:
# Timeout: report the wait result (WAIT_TIMEOUT=258 / WAIT_FAILED=4294967295) and elapsed.
# The candidate path and captured content are per-probe detail shown only with --verbose.
VLog.LogFmt3(" 警告: --version 探测超时 (rc=2, wait=%u, 上限 %u ms),已等待 %u ms 后终止子进程\n",
VLog.LogFmt3(" Warning: --version probe timeout (rc=2, wait=%u, limit %u ms), child process terminated after %u ms\n",
sizet(Wr), sizet(VERSION_PROBE_TIMEOUT_MS), KillMs)
if total == 0:
VLog.VerboseStr(" 警告: 超时前已捕获 0 字节(空)\n")
VLog.VerboseStr(" Warning: captured 0 bytes before timeout (empty)\n")
else:
VLog.VerboseFmt1(" 警告: 超时前已捕获 %u 字节,内容:\n", total)
VLog.VerboseFmt1(" Warning: captured %u bytes before timeout, content:\n", total)
VLog.VerboseStr(str(Out))
VLog.VerboseStr("\n")
if Trace != 0:
VLog.VerboseFmt4(" [--version] 结束: 耗时 %u ms, 退出码 %u, 超时 %u, 输出 %u 字节\n",
VLog.VerboseFmt4(" [--version] end: elapsed %u ms, exit code %u, timeout %u, output %u bytes\n",
Elapsed, sizet(ExitCode), sizet(TimedOut), total)
if TimedOut == 0:
# Normal exit (rc=0): captured byte count and first line are per-probe detail (verbose only).
Line: bytes = stdlib.malloc(PROBE_LINE_CAP)
if Line is None:
VLog.VerboseFmt1(" [--version] 捕获 %u 字节(首行缓冲分配失败)\n", total)
VLog.VerboseFmt1(" [--version] captured %u bytes (first-line buffer allocation failed)\n", total)
else:
CopyFirstLine(Out, Line, PROBE_LINE_CAP)
if total == 0:
VLog.VerboseStr(" [--version] 捕获 0 字节(空)\n")
VLog.VerboseStr(" [--version] captured 0 bytes (empty)\n")
else:
VLog.VerboseFmtIS(" [--version] 捕获 %u 字节, 首行: %s\n", total, str(Line))
VLog.VerboseFmtIS(" [--version] captured %u bytes, first line: %s\n", total, str(Line))
stdlib.free(t.CPtr(Line))
if TimedOut != 0:
return 2
@@ -235,28 +235,28 @@ def RunVersionPrefixCheck(Target: str) -> int:
OutCap: sizet = sizet(VERSION_PROBE_DIAG_CAP)
Out: bytes = stdlib.malloc(OutCap)
if Out is None:
VLog.LogStr(" 分配缓冲区失败\n")
VLog.LogStr(" buffer allocation failed\n")
return 1
rc: int = RunVersionProbe(Target, Out, OutCap, 0)
if rc == 1:
VLog.LogStr(" 子进程启动失败\n")
VLog.LogStr(" child process launch failed\n")
stdlib.free(t.CPtr(Out))
return 1
if rc == 2:
VLog.LogStr(" 探测超时,判定失败\n")
VLog.LogStr(" probe timeout, marked as failure\n")
stdlib.free(t.CPtr(Out))
return 1
VLog.LogFmt1(" 退出码: %d\n", sizet(uint32(Probe_ExitCode)))
VLog.LogFmt1(" exit code: %d\n", sizet(uint32(Probe_ExitCode)))
out: str = str(Out)
if out is None:
VLog.LogStr(" 未捕获到 stdout\n")
VLog.LogStr(" no stdout captured\n")
stdlib.free(t.CPtr(Out))
return 1
head: bytes = stdlib.malloc(6)
if head is None:
VLog.LogStr(" 分配缓冲区失败\n")
VLog.LogStr(" buffer allocation failed\n")
stdlib.free(t.CPtr(Out))
return 1
k: int = 0
@@ -266,13 +266,13 @@ def RunVersionPrefixCheck(Target: str) -> int:
head[k] = 0
if string.strncmp(out, VERSION_PREFIX, 5) == 0:
VLog.LogFmtS1(" 前 5 字符: %s\n", str(head))
VLog.LogFmtS1(" first 5 chars: %s\n", str(head))
stdlib.free(t.CPtr(head))
stdlib.free(t.CPtr(Out))
return 0
VLog.LogFmtS2(" 前 5 字符: %s(期望为 %s)\n", str(head), VERSION_PREFIX)
VLog.LogStr(" 原始输出:\n")
VLog.LogFmtS2(" first 5 chars: %s (expected %s)\n", str(head), VERSION_PREFIX)
VLog.LogStr(" raw output:\n")
VLog.LogStr(out)
VLog.LogStr("\n")
stdlib.free(t.CPtr(head))
+28 -28
View File
@@ -160,17 +160,17 @@ def ProgReset(RoundIdx: int, Roots: int):
Prog_Cands = 0
Prog_Cur = None
VLog.TimeStampNow(Prog_Stamp, PROG_STAMP_CAP)
VLog.VerboseFmt2("==== 扫盘轮次 %u 开始 (roots=%u", sizet(RoundIdx), sizet(Roots))
VLog.VerboseFmt2("==== scan round %u start (roots=%u", sizet(RoundIdx), sizet(Roots))
VLog.VerboseFmtS1(", t=%s) ====\n", str(Prog_Stamp))
def ProgRootEnter(Dir: str):
VLog.VerboseFmtS1(" [root] 进入: %s\n", Dir)
VLog.VerboseFmtS1(" [root] enter: %s\n", Dir)
def ProgRootLeave(Dir: str, ElapsedMs: sizet):
global Prog_Cur
VLog.VerboseFmtIS(" [root] 离开 (耗时 %u ms): %s\n", ElapsedMs, Dir)
VLog.VerboseFmtIS(" [root] leave (%u ms elapsed): %s\n", ElapsedMs, Dir)
Prog_Cur = None # path buffer is freed after this root; avoid a dangling pointer
@@ -216,15 +216,15 @@ def ScanInit() -> int:
Prog_Elapsed = _mbuddy.alloc(PROG_ELAPSED_CAP)
if Scr_Ver is None or Scr_Arch is None or Scr_Id is None or Scr_Disp is None \
or Probe_Buf is None or Prog_Stamp is None or Prog_Elapsed is None:
VLog.LogStr("[VET] FATAL: 扫盘缓冲分配失败\n")
VLog.LogStr("[VET] FATAL: scan buffer allocation failed\n")
return 0
if VJson.JsonInit() == 0:
VLog.LogStr("[VET] FATAL: 扫盘缓冲分配失败\n")
VLog.LogStr("[VET] FATAL: scan buffer allocation failed\n")
return 0
# check channel buffers/reply pool: on failure degrade only (check replies
# CHANNEL_UNAVAILABLE, no revalidation removal); scan itself is unaffected.
if VCheck.CheckInit() == 0:
VLog.LogStr("警告: check 通道缓冲不可用,check 请求只回执失败原因(CHANNEL_UNAVAILABLE)、每轮不重校已发现项\n")
VLog.LogStr("Warning: check channel buffers unavailable; check requests only get the failure reason (CHANNEL_UNAVAILABLE), discovered entries are not revalidated each round\n")
return 1
@@ -258,17 +258,17 @@ def HandleCandidate(ExeAnsi: str) -> int:
# Poll the check channel before probing (a probe can take 5s; check outranks scanning).
VGuard.PollIpc()
if ProbeVersion(ExeAnsi) == 0:
VLog.VerboseFmtS1(" [跳过] PE+PRVERSN 命中但 --version 前缀不符: %s\n", ExeAnsi)
VLog.VerboseFmtS1(" [skip] PE+PRVERSN hit but --version prefix mismatch: %s\n", ExeAnsi)
return 0
if VGuid.NewGuidStr(Scr_Id, VGuid.GUID_STR_CAP) == 0:
VLog.LogStr(" 警告: CoCreateGuid 失败,跳过该条目\n")
VLog.LogStr(" Warning: CoCreateGuid failed, skipping this entry\n")
return 0
viperlib.snprintf(Scr_Disp, sizet(VCache.DISP_CAP), "Viper %s", Scr_Ver)
if VCache.CacheAdd(ExeAnsi, str(Scr_Id), str(Scr_Ver), str(Scr_Arch),
"workspace", str(Scr_Disp)) == 0:
return 0
Round_Added += 1
VLog.VerboseFmtS2(" [命中] %s (版本 %s)\n", ExeAnsi, str(Scr_Ver))
VLog.VerboseFmtS2(" [hit] %s (version %s)\n", ExeAnsi, str(Scr_Ver))
return 1
@@ -349,7 +349,7 @@ def RevalidateDiscovered() -> int:
Exe: str = VCache.CacheExe(j) # fetch first (RemoveAt frees this slot)
VGuard.PollIpc() # poll before probe (check first)
if VCheck.VerifyExecutable(Exe) == 0:
VLog.VerboseFmtS1(" [重校剔除] %s\n", Exe)
VLog.VerboseFmtS1(" [revalidate-remove] %s\n", Exe)
VCache.RemoveAt(j)
removed += 1
else:
@@ -391,13 +391,13 @@ def RunScanRound(CachePath: str, RoundIdx: int) -> int:
else:
# Static auto-table entries and drive letters may not exist; logging each is
# process detail, given only with --verbose (explicit --path warnings are in VDirs).
VLog.VerboseFmtS1(" 跳过(不存在或非目录): %s\n", str(AnsiDir))
VLog.VerboseFmtS1(" skipped (missing or not a directory): %s\n", str(AnsiDir))
_mbuddy.free(t.CPtr(AnsiDir))
# Candidates filtered by PE/.PRVERSN: first REJECT_LOG_MAX were logged in HandleCandidate;
# only a summary line is added here (both are process detail, --verbose only).
if Reject_Total > REJECT_LOG_MAX:
VLog.VerboseFmt2(" [reject] 其余省略…(本轮累计 %u 条,已逐条打印前 %u 条)\n",
VLog.VerboseFmt2(" [reject] rest omitted... (round total %u, first %u printed individually)\n",
sizet(Reject_Total), sizet(REJECT_LOG_MAX))
# Final poll before round end: no wait point before writing, so check hits enter this round's array
@@ -410,14 +410,14 @@ def RunScanRound(CachePath: str, RoundIdx: int) -> int:
T1: ULONGLONG = wb.GetTickCount64()
# Round-end line (kept by default, no paths): liveness signal, or the final line under --once.
# "Removed" = revalidation removals (Revalidated) + existence-check removals (removed).
VLog.LogFmt3("轮次 %u 结束: 扫描目录 %u 个, 已知条目 %u 项",
VLog.LogFmt3("round %u end: scanned dirs %u, known entries %u",
sizet(RoundIdx), sizet(Dirs), sizet(VCache.CacheCount()))
VLog.LogFmt3(", 新增 %u 项, 移除 %u 项, 耗时 %u ms",
VLog.LogFmt3(", added %u, removed %u, elapsed %u ms",
sizet(Round_Added), sizet(Revalidated + removed), sizet(T1 - T0))
# next-round-start timestamp is process detail (--verbose only)
if VLog.VerboseOn() != 0:
VLog.TimeStampAfter(Prog_Stamp, PROG_STAMP_CAP, sizet(VCli.SleepMs()))
VLog.LogFmtS1(", 下轮开始: %s", str(Prog_Stamp))
VLog.LogFmtS1(", next round starts: %s", str(Prog_Stamp))
VLog.LogStr("\n")
return 0
@@ -442,28 +442,28 @@ def RunScan() -> int:
Pace_Configured = 1
Pace_Sleep_Ms = VCli.PaceSleepMs()
Pace_File_Units = VCli.PaceFileUnits()
VLog.VerboseFmt2("--pace: 已启用扫描节流 sleep=%u units=%u\n",
VLog.VerboseFmt2("--pace: scan throttling enabled sleep=%u units=%u\n",
sizet(Pace_Sleep_Ms), sizet(Pace_File_Units))
elif Once != 0:
# No explicit throttle args: --once runs one round with throttle sleep disabled
Pace_Enabled = 0
Pace_Configured = 0
VLog.VerboseStr("--once: 已关闭扫描节流 sleep\n")
VLog.VerboseStr("--once: scan throttling sleep disabled\n")
# --first-no-pace: affects round 1's actual switch only (RunScanRound restores + applies it);
# so do not set Pace_Enabled here, just log one line (process detail, --verbose only).
if VCli.FirstNoPace() != 0:
VLog.VerboseStr("--first-no-pace: 第 1 轮不启用节流 sleep\n")
VLog.VerboseStr("--first-no-pace: round 1 runs without throttle sleep\n")
if ScanInit() == 0:
return 1
VLog.VerboseStr("[0/3] 常用目录表\n")
VLog.VerboseStr("[0/3] common directory table\n")
rc0: int = VDirs.BuildTable()
if rc0 == 0:
VLog.VerboseStr(" ✓ 通过\n\n")
VLog.VerboseStr(" ✓ pass\n\n")
else:
# Empty table = no scan roots this round: a functional anomaly, still reported by default
VLog.LogStr("警告: 常用目录表为空(本轮无扫描根)\n\n")
VLog.LogStr("Warning: common directory table is empty (no scan roots this round)\n\n")
# Cache load moved to each round start (RunScanRound -> VCache.ReloadCache); no separate
# read here, avoiding double parsing in round 1 (--once also goes through round 1).
@@ -485,18 +485,18 @@ def RunScan() -> int:
# stderr, return 0. Reuses VDirs.BuildTable; no scanning, no cache, exit code always 0.
def RunSearchPathsQuery() -> int:
VDirs.BuildTable()
Src: str = "自动表"
Src: str = "auto table"
if VDirs.IsOverride() != 0:
Src = "覆盖表 (--path)"
VLog.LogFmtS1("[search] 目录表来源: %s\n", Src)
Src = "override table (--path)"
VLog.LogFmtS1("[search] directory table source: %s\n", Src)
N: int = VDirs.DirCount()
if N <= 0:
# empty table must be stated explicitly so the user does not think it hung
VLog.LogStr("[search] 没有可搜索的路径(常用目录表为空)\n")
VLog.LogStr("[search] no searchable paths (common directory table is empty)\n")
return 0
Ansi: bytes = _mbuddy.alloc(VFs.PATH_BUF_CAP)
if Ansi is None:
VLog.LogStr(" 警告: 查询缓冲分配失败,仅列出路径(不标注是否会被扫盘跳过)\n")
VLog.LogStr(" Warning: query buffer allocation failed, listing paths only (no mark for scan-skip)\n")
for i in range(N):
Path: str = VDirs.DirAt(i)
Skip: int = 0 # 1 = will be skipped when scanning; stays 0 if Ansi unavailable
@@ -505,10 +505,10 @@ def RunSearchPathsQuery() -> int:
if VFs.PathIsDir(str(Ansi)) == 0:
Skip = 1
if Skip != 0:
VLog.LogFmtIS(" [search] [%u] %s (不存在或非目录,扫盘时会跳过)\n", sizet(i), Path)
VLog.LogFmtIS(" [search] [%u] %s (missing or not a directory, skipped when scanning)\n", sizet(i), Path)
else:
VLog.LogFmtIS(" [search] [%u] %s\n", sizet(i), Path)
if Ansi is not None:
_mbuddy.free(t.CPtr(Ansi))
VLog.LogFmt1(" [search] 合计 %u 条;未扫盘、未读写缓存,查询到此结束\n", sizet(N))
VLog.LogFmt1(" [search] total %u; no scan, no cache access, query ends here\n", sizet(N))
return 0
+4 -4
View File
@@ -45,11 +45,11 @@ def main() -> int:
# MemBuddy pool: sys._init_argv and all module allocations depend on it
arena: bytes = stdlib.malloc(POOL_SIZE)
if arena is None:
VLog.LogStr("[VET] FATAL: 内存池分配失败\n")
VLog.LogStr("[VET] FATAL: memory pool allocation failed\n")
return 1
mb: memhub.MemBuddy* = memhub.MemBuddy(arena, POOL_SIZE)
if mb is None:
VLog.LogStr("[VET] FATAL: MemBuddy 初始化失败\n")
VLog.LogStr("[VET] FATAL: MemBuddy initialization failed\n")
return 1
# Inject the same pool into every module that declares _mbuddy; missing one makes its
@@ -95,7 +95,7 @@ def main() -> int:
# Failure is fatal: an explicit heartbeat request must not silently run unconfirmed.
if VCli.NeedConfirm() > 0:
if VGuard.SpawnWatchdog(VCli.NeedConfirm(), VCli.NeedConfirmTimeout()) == 0:
VLog.LogStr("错误: 心跳确认看门狗启动失败,已中止 scan\n")
VLog.LogStr("Error: heartbeat confirm watchdog launch failed, scan aborted\n")
return 1
RcScan: int = VScan.RunScan()
VGuard.StopWatchdog() # Always reap the watchdog on normal exit (incl. --once) to avoid orphans
@@ -103,6 +103,6 @@ def main() -> int:
# Hidden internal subcommand: heartbeat watchdog (not in usage; spawned by the scan branch)
if string.strcmp(Cmd, "confirm-watch") == 0:
return VGuard.RunConfirmWatch()
VLog.LogFmtS1("错误: 未知子命令 %s\n", Cmd)
VLog.LogFmtS1("Error: unknown subcommand %s\n", Cmd)
VCli.PrintUsage()
return 1