Files
Viper_VET/App/lib/VCli.vp
T
TermiNexus 02df100204 chore(vet): 初始化 VET 仓(Viper Environment Tool)
首次入库,远端 https://git.gvsds.com/GVSDS/Viper_VET 。

19 个文件全部为源码/配置,无构建产物:
  App/main.vp、App/lib/*.vp(VCache / VCheck / VCli / VDirs / VFs / VGuard /
    VGuid / VJson / VLog / VPe / VProc / VScan / __init__)
  1.json、LICENSE、README.md、project.vpj、.gitignore

既有 .gitignore 已覆盖本仓的产物目录(**/output/、**/temp/、**/.tpv_build/)
与 *.exe / *.obj / *.o / *.img / *.iso / __pycache__/ / .cache/;
实测 `git add -A --dry-run` 仅 19 个文件、零产物混入,故未改动 .gitignore。
2026-09-29 15:20:13 +08:00

539 lines
29 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import t, c
from stdint import *
import string
import sys
import lib.VLog as VLog
import lib.VDirs as VDirs
# ============================================================
# VCli - 命令行解析(scan/ver、--cache/--once/--sleep/--pace/--pace-units/--need-confirm/
# --need-confirm-timeout/--path/--verbose/--search-paths;手写解析,不引入 argparse)
#
# 从原 App/main.vp 的 RunScan 参数解析段平移,并新增可重复的 --path(需求 2):
# 一旦出现 --path 即置 VDirs 覆盖模式(仅收集路径、保序);存在性检查与
# "不存在则告警并跳过该条"在 VDirs.BuildTable([0/3] 段内)执行。
# --need-confirm <秒>:心跳确认间隔,缺省/0 = 关闭该功能(仅 scan 模式生效)。
# 本轮改为"独立看门狗子进程"实现(见 VGuard.vp):父进程在本模块解析出
# NeedConfirm/NeedConfirmTimeout 后由 App/main.vp 拉起子进程,扫盘侧不再参与确认。
# --sleep <毫秒>:手动指定扫盘轮间隔(缺省 DEFAULT_SLEEP_MS=10000;0 = 不 sleep)。
# --pace <毫秒>:扫盘节流点的睡眠时长(缺省 DEFAULT_PACE_SLEEP_MS=20;0 = 关闭节流)。
# --pace-units <N>:文件侧节流粒度——每 N 个文件一个节流点(缺省 DEFAULT_PACE_FILE_UNITS=64;1 = 每个文件)。
# 两者"显式给出即启用":只要命令行出现其一,VScan 便打开节流(--once 下也生效);
# 未给出时 VScan 完全沿用其自身缺省(常驻启用/20ms/64,--once 关闭),行为逐字不变。
# 目录侧粒度固定为 VScan.PACE_DIR_UNITS=16,不受 --pace-units 影响;--pace 0 时两个节流点都不 Sleep。
# 取值经 PaceSet()/PaceSleepMs()/PaceFileUnits() 暴露给 VScan.RunScan。
# --need-confirm-timeout <毫秒>:看门狗确认等待超时(缺省 DEFAULT_CONFIRM_TIMEOUT_MS=5000)。
# --verbose(无值):打开详细过程日志(缺省 = 安静)。仅改变 stderr 日志详细度,
# 不影响 stdout 的单行 JSON 与缓存文件内容。取值经 Verbose() 暴露,由 App/main.vp
# 在参数解析后、任何日志之前设置到 VLog(VLog.SetVerbose)。
# --search-paths(无值):**查询开关**——建好"会搜索的目录表"后逐行打印(stderr)并直接退出,
# 不扫盘、不拉起看门狗、不读写缓存,因此与 --cache 相互独立:未给 --cache 也可运行
# (见 ParseScanArgs 末尾的必填校验)。取值经 SearchPaths() 暴露,由 App/main.vp 在 scan
# 分派中"拉起看门狗之前"分派到 VScan.RunSearchPathsQuery。
# confirm-watch(内部隐藏子命令)的参数解析在 ParseWatchArgs(),不出现在用法里;
# 父进程为 verbose 时为子进程追加 --verbose,使看门狗按同一规则输出(WatchVerbose())。
# ver 子命令的取参(sys._argv[2])在 VPe.RunVer 内,保持原样;ver 不解析开关,
# 其 verbose 由 HasVerbose()(argv 中出现 --verbose 即置位)识别。
# 取参方式依据:sys._init_argv/_argc/_argv(includes/sys.vp:60-67)。
# ============================================================
VET_VERSION: t.CDefine = "0.2.0"
SECS_MAX: t.CDefine = 86400 # --need-confirm 秒数上界(1 天;超出视为参数错误)
DEFAULT_SLEEP_MS: t.CDefine = 10000 # --sleep 缺省值(沿用原 SCAN_INTERVAL_MS)
SLEEP_MAX_MS: t.CDefine = 86400000 # --sleep 毫秒上界(1 天;超出视为参数错误)
DEFAULT_CONFIRM_TIMEOUT_MS: t.CDefine = 5000 # --need-confirm-timeout 缺省值
CONFIRM_TIMEOUT_MAX_MS: t.CDefine = 86400000 # --need-confirm-timeout 毫秒上界(1 天)
# --pace <毫秒>:节流点睡眠时长,取值域 0..60000。0 = 关闭节流(两个节流点都不 Sleep)。
# 上界取 1 分钟:节流只为降低扫盘对机器的持续压力,睡眠远超 1 分钟只会无谓拖慢单轮而无收益,
# 与 --sleep(轮间隔,上界 1 天)的用途不同,故不共用其宽松上界。
DEFAULT_PACE_SLEEP_MS: t.CDefine = 20 # --pace 缺省值(沿用 VScan.PACE_SLEEP_MS,未给出时逐字保持现状)
PACE_SLEEP_MAX_MS: t.CDefine = 60000 # --pace 毫秒上界(1 分钟)
# --pace-units <N>:文件粒度,取值域 1..1000000(每 N 个文件一个节流点;1 = 每个文件都停)。
# 0 无意义("每 0 个文件"),与非法输入一并报错;上界 1000000 已足够稀疏(等同关闭文件侧节流)。
DEFAULT_PACE_FILE_UNITS: t.CDefine = 64 # --pace-units 缺省值(沿用 VScan.PACE_FILE_UNITS,未给出时逐字保持现状)
PACE_UNITS_MAX: t.CDefine = 1000000 # --pace-units 文件数上界
# --parent-pid 上界(DWORD 全域 4294967295)。
# 注意:这里不能用 `t.CDefine = 4294967295`。CDefine 的物化规则(TPV
# ExprGenCdef._const_int_for_cdefine)把落在 u32 范围内的值一律按 **i32** 常量生成
# (0xFFFFFFFF → i32 -1),上一轮构建 IR 现场:
# VET/.tpv_build/temp/df/df86f78f0f0481a1.ll:1214
# %108 = call i32 @...ParseUint(i8* %107, i32 -1)
# 即 PID_MAX 传到 `Max: int` 时已成 -1。改宽类型全局常量后该值以 i64 物化
# (字面量对 >32 位目标走 const_int64,ExprGenConst.translate_constant:105-107)。
PID_MAX: LONGLONG = 4294967295 # --parent-pid 上界(DWORD 全域)
Cache_Path: str = None
Once_Flag: int = 0
Need_Confirm: int = 0
Sleep_Ms: int = DEFAULT_SLEEP_MS
Need_Confirm_Timeout: int = DEFAULT_CONFIRM_TIMEOUT_MS
Verbose_Flag: int = 0 # --verbose(scan);1=详细日志
# 顶层写法(`VET.exe --search-paths ...`,子命令位置直接写该开关)时由 App/main.vp 置位:
# ParseScanArgs 从 argv[2] 起扫并先把 Search_Paths_Flag 清零,故"子命令位置上的那个
# --search-paths"它扫不到;用本标记在解析后重新并入,两种写法语义完全一致。
Search_Paths_Implicit: int = 0
Search_Paths_Flag: int = 0 # --search-paths(scan);1=打印将搜索的路径后直接退出
# --pace/--pace-units(scan):显式给出即启用节流(--once 下也生效)。
# Pace_Set 记"是否显式给出过两者之一";未给出时 VScan 完全沿用其缺省(常驻启用/20ms/64,--once 关闭)。
Pace_Set: int = 0 # 1=命令行显式给出 --pace 或 --pace-units
Pace_Ms: int = DEFAULT_PACE_SLEEP_MS # --pace <毫秒>(0 = 关闭节流)
Pace_Units: int = DEFAULT_PACE_FILE_UNITS # --pace-units <N>(每 N 个文件一个节流点;1 = 每个文件)
# --first-no-pace(scan):第一轮扫盘不启用节流(之后各轮按常规规则)。
# 只影响"第 1 轮"这一件事:不改变 Pace_Set/Pace_Ms/Pace_Units,也不改变常驻/--once 的常规节流规则;
# 与 --pace 0(所有轮都不节流)互不替代——前者是"首轮抢速度",后者是"全程不限速"。
First_No_Pace_Flag: int = 0 # 1=第一轮不启用节流
# confirm-watch(隐藏内部子命令)参数
Watch_Interval: int = 0 # 秒(父进程由 --need-confirm 传入)
Watch_Timeout: int = -1 # 毫秒;-1 = 未给出(之后取 DEFAULT_CONFIRM_TIMEOUT_MS)
Watch_ParentPid: LONGLONG = 0 # 父进程 pid(宽类型:承载 DWORD 全域,见 PID_MAX)
Watch_IpcWrite: ULONGLONG = 0 # check 通道管道**写端句柄数值**(0 = 未给出 ⇒ 无通道)
Watch_Verbose: int = 0 # --verbose(由父进程在 verbose 时追加传入)
# ParseUint64 的出参(模块全局,与 VPe.Pe_RawPtr / VFs.LastReadSize 同一惯例)
U64_Val: ULONGLONG = 0
# 版本号经函数暴露给 VPe/VScan(跨模块"函数调用"路径明确支持;
# 避免依赖跨模块字符串 t.CDefine 取值——本仓已证实的是跨模块整型 t.CDefine)
def Version() -> str:
return VET_VERSION
def CachePath() -> str:
return Cache_Path
def Once() -> int:
return Once_Flag
def NeedConfirm() -> int:
return Need_Confirm
def SleepMs() -> int:
return Sleep_Ms
def NeedConfirmTimeout() -> int:
return Need_Confirm_Timeout
def Verbose() -> int:
return Verbose_Flag
def SearchPaths() -> int:
return Search_Paths_Flag
# --pace/--pace-units 取值:PaceSet()==0 时调用方(VScan.RunScan)应完全忽略后两者、沿用自身缺省,
# 以保证"未给出参数时行为逐字不变";==1 时三者的组合语义见 RunScan 的注释。
def PaceSet() -> int:
return Pace_Set
def PaceSleepMs() -> int:
return Pace_Ms
def PaceFileUnits() -> int:
return Pace_Units
# --first-no-pace:是否要求"第 1 轮不启用节流"。1=是(仅第 1 轮;之后各轮按常规规则)。
def FirstNoPace() -> int:
return First_No_Pace_Flag
# 顶层写法专用:`VET.exe --search-paths ...`(子命令位置就是该开关)。
# 必须在 ParseScanArgs 之前调用——它把该意图记进 Search_Paths_Implicit,解析结束时再并入
# Search_Paths_Flag;这样解析里"Search_Paths_Flag == 0 才要求 --cache"的放宽也一并生效。
def SetSearchPathsImplicit():
global Search_Paths_Implicit
Search_Paths_Implicit = 1
def WatchInterval() -> int:
return Watch_Interval
def WatchTimeout() -> int:
return Watch_Timeout
def WatchParentPid() -> LONGLONG:
return Watch_ParentPid
def WatchIpcWrite() -> ULONGLONG:
return Watch_IpcWrite
def WatchVerbose() -> int:
return Watch_Verbose
# ver 子命令专用:ver 不解析开关(目标路径固定在 sys._argv[2]),
# 只要 argv 中出现 --verbose 即视为要求详细日志(argv 起点与 scan 一致,从下标 2 扫)。
def HasVerbose() -> int:
i: int = 2
while i < sys._argc:
A: str = sys._argv[i]
if A is not None:
if string.strcmp(A, "--verbose") == 0:
return 1
i += 1
return 0
# 严格解析十进制非负整数:仅接受 '0'-'9'(空串/空白/正负号/小数点/其它字符 → -1=非法),
# 上界 Max(超出 → -1,避免无意义超大间隔与运算溢出)。
# 不用 string.atoi(includes/string.vp:271):它对非法尾字符静默截断("3x" → 3),
# 与本参数"整数字面量"的约定不符。
#
# 宽度(本函数修的就是这一条):Max / 累加器 v / 返回值统一用 LONGLONG(有符号 64 位)。
# * 原 `Max: int`(i32)配 PID_MAX=4294967295 会被截断成 -1,于是 `v > Max`
# 对任何首位数字恒真 ⇒ 任何 --parent-pid 都判非法(看门狗启动即 exit 1)。
# IR 证据:本仓上一轮构建 IR
# VET/.tpv_build/temp/df/df86f78f0f0481a1.ll:521
# define i32 @df86f78f0f0481a1.ParseUint(i8* %S, i32 %Max) ; Max 是 i32
# VET/.tpv_build/temp/df/df86f78f0f0481a1.ll:583
# %35 = icmp sgt i32 %34, %Max ; 有符号比较,Max=-1
# VET/.tpv_build/temp/df/df86f78f0f0481a1.ll:1214
# %108 = call i32 @df86f78f0f0481a1.ParseUint(i8* %107, i32 -1)
# 后跟 :1217 `%110 = icmp sle i32 %109, 0` ⇒ P<=0 恒真 ⇒ 打错误并 ret 1。
# * 不用 t.CSizeT/ULONGLONG:无符号返回承载不了 -1 非法哨兵,且各调用点的
# `if X < 0`(本文件 :165/:180/:196/:253/:264/:275)在无符号比较下恒假 ⇒ 非法值被放行。
# * 溢出(20 位数字也不会):循环顶部 `if v > Max: return -1` 是不变量守卫——
# 进入每轮时必有 v <= Max,故 v*10 <= Max*10;本模块全部上界 <= 4294967295,
# 远小于 i64 上限/10,`v*10 + d` 不可能溢出;末尾 `or v < 0` 再兜一层 64 位
# 回绕(防御性,按当前上界不可达)。
def ParseUint(S: str, Max: LONGLONG) -> LONGLONG:
if S is None:
return -1
ub: BYTEPTR = BYTEPTR(S)
if ub[0] == 0:
return -1
v: LONGLONG = 0
i: int = 0
while ub[i] != 0:
ch: int = int(ub[i])
if ch < 48 or ch > 57:
return -1
if v > Max: # 上界短路:兼作 v*10 的溢出守卫(不变量兜底)
return -1
v = v * 10 + LONGLONG(ch - 48)
if v > Max or v < 0: # 越界 / 64 位回绕(后者理论不可达)
return -1
i += 1
return v
# --need-confirm 的秒数解析:等价 ParseUint(S, SECS_MAX)(保持原判定口径与错误码 -1)
def ParseSecs(S: str) -> int:
return ParseUint(S, SECS_MAX)
# 严格解析十进制**无符号 64 位**字面量:仅接受 '0'-'9'(空串/空白/正负号/小数点/其它字符/溢出 ⇒ 失败)。
# 成功返回 1 并把值写入 U64_Val;失败返回 0(U64_Val 置 0)。
#
# 为什么不复用 ParseUint:`--ipc-write` 传的是**句柄的位模式**(父进程 ptrtoint 得到)。
# 64 位 Windows 上内核句柄按符号扩展存放(最高位为 1),其无符号读数超过 INT64_MAX,
# 而 ParseUint 的返回域(LONGLONG)与 -1 非法哨兵都承载不了它;若截成 32 位传,符号扩展型
# 句柄在子进程侧就还原不回原值(inttoptr 得到另一个地址)。故这里按 u64 全域解析。
# 溢出守卫:不变量 v*10 + d ≤ 2^64-1 = 18446744073709551615 ⇒ v ≤ 1844674407370955161,
# 且 v 取该上界时 d 必须 ≤ 5。
def ParseUint64(S: str) -> int:
global U64_Val
U64_Val = 0
if S is None:
return 0
ub: BYTEPTR = BYTEPTR(S)
if ub[0] == 0:
return 0
v: ULONGLONG = 0
i: int = 0
while ub[i] != 0:
ch: int = int(ub[i])
if ch < 48 or ch > 57:
return 0
d: ULONGLONG = ULONGLONG(ch - 48)
if v > ULONGLONG(1844674407370955161): # 再乘 10 必溢出
return 0
if v == ULONGLONG(1844674407370955161) and d > ULONGLONG(5):
return 0
v = v * ULONGLONG(10) + d
i += 1
U64_Val = v
return 1
def PrintUsage():
VLog.LogStr("VET - Viper Environment Tool\n")
VLog.LogStr("用法:\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(" [--verbose] [--path <dir>]...\n")
VLog.LogStr(" 常驻扫盘发现器;结果 JSON 写 stdout,缓存写 <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")
# 解析 scan 参数。返回 0=成功,1=参数错误(已打印用法/错误)
# 说明:本轮起 ParseScanArgs 由 App/main.vp 在分派 scan 时调用(看门狗生命周期需要
# 在 RunScan 之前拿到 NeedConfirm),VScan.RunScan 不再自行解析。
def ParseScanArgs() -> int:
global Cache_Path, Once_Flag, Need_Confirm, Sleep_Ms, Need_Confirm_Timeout, Verbose_Flag
global Search_Paths_Flag, Pace_Set, Pace_Ms, Pace_Units, First_No_Pace_Flag
Cache_Path = None
Once_Flag = 0
Need_Confirm = 0
Sleep_Ms = DEFAULT_SLEEP_MS
Need_Confirm_Timeout = DEFAULT_CONFIRM_TIMEOUT_MS
Verbose_Flag = 0
Search_Paths_Flag = 0
Pace_Set = 0
Pace_Ms = DEFAULT_PACE_SLEEP_MS
Pace_Units = DEFAULT_PACE_FILE_UNITS
First_No_Pace_Flag = 0
if Search_Paths_Implicit != 0:
# 顶层写法(`VET.exe --search-paths ...`):该开关位于 argv[1](子命令位置),
# 本函数从 argv[2] 起扫,扫不到它 ⇒ 由 main.vp 预先置位,这里并入(见其声明处)。
Search_Paths_Flag = 1
Has_Timeout: int = 0
i: int = 2
while i < sys._argc:
A: str = sys._argv[i]
if A is not None:
if string.strcmp(A, "--cache") == 0:
if i + 1 < sys._argc and sys._argv[i + 1] is not None:
Cache_Path = sys._argv[i + 1]
i += 1
else:
VLog.LogStr("错误: --cache 需要一个路径参数\n")
return 1
elif string.strcmp(A, "--once") == 0:
Once_Flag = 1
elif string.strcmp(A, "--verbose") == 0:
Verbose_Flag = 1 # 无值开关:只改 stderr 详细度
elif string.strcmp(A, "--search-paths") == 0:
Search_Paths_Flag = 1 # 无值开关:查询"会搜索的路径"后直接退出(不扫盘/不碰缓存)
elif string.strcmp(A, "--sleep") == 0:
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 的整数字面量毫秒数,实际为: ",
t.CSizeT(SLEEP_MAX_MS))
VLog.LogFmtS1("%s\n", sys._argv[i + 1])
PrintUsage()
return 1
Sleep_Ms = M
i += 1
else:
VLog.LogStr("错误: --sleep 需要一个毫秒数参数\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 的整数字面量毫秒数,实际为: ",
t.CSizeT(PACE_SLEEP_MAX_MS))
VLog.LogFmtS1("%s\n", sys._argv[i + 1])
PrintUsage()
return 1
Pace_Ms = P
Pace_Set = 1 # 显式给出 ⇒ 节流启用(--once 下也生效)
i += 1
else:
VLog.LogStr("错误: --pace 需要一个毫秒数参数\n")
PrintUsage()
return 1
elif string.strcmp(A, "--pace-units") == 0:
if i + 1 < sys._argc and sys._argv[i + 1] is not None:
U: int = ParseUint(sys._argv[i + 1], PACE_UNITS_MAX)
# 0 与非法同为错误(ParseUint 非法返回 -1):0 会让"每 0 个文件一个节流点"无意义
if U <= 0:
VLog.LogFmt1("错误: --pace-units 需要一个 1..%u 的整数字面量文件数,实际为: ",
t.CSizeT(PACE_UNITS_MAX))
VLog.LogFmtS1("%s\n", sys._argv[i + 1])
PrintUsage()
return 1
Pace_Units = U
Pace_Set = 1 # 显式给出 ⇒ 节流启用(--once 下也生效)
i += 1
else:
VLog.LogStr("错误: --pace-units 需要一个文件数参数\n")
PrintUsage()
return 1
elif string.strcmp(A, "--first-no-pace") == 0:
# 无值开关:只要求"第 1 轮不节流",不改变节流参数本身、也不置 Pace_Set
# (第 2 轮起的节流规则与未给该开关时逐字相同)。
First_No_Pace_Flag = 1
elif string.strcmp(A, "--need-confirm") == 0:
if i + 1 < sys._argc and sys._argv[i + 1] is not None:
S: int = ParseSecs(sys._argv[i + 1])
if S < 0:
# 两个固定元数调用拼一行(LogFmt1 只带整数,LogFmtS1 只带字符串)
VLog.LogFmt1("错误: --need-confirm 需要一个 0..%u 的整数字面量秒数,实际为: ",
t.CSizeT(SECS_MAX))
VLog.LogFmtS1("%s\n", sys._argv[i + 1])
PrintUsage()
return 1
Need_Confirm = S
i += 1
else:
VLog.LogStr("错误: --need-confirm 需要一个秒数参数\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 的整数字面量毫秒数,实际为: ",
t.CSizeT(CONFIRM_TIMEOUT_MAX_MS))
VLog.LogFmtS1("%s\n", sys._argv[i + 1])
PrintUsage()
return 1
Need_Confirm_Timeout = T
Has_Timeout = 1
i += 1
else:
VLog.LogStr("错误: --need-confirm-timeout 需要一个毫秒数参数\n")
PrintUsage()
return 1
elif string.strcmp(A, "--path") == 0:
if i + 1 < sys._argc and sys._argv[i + 1] is not None:
VDirs.BeginOverride()
VDirs.AddOverridePath(sys._argv[i + 1])
i += 1
else:
VLog.LogStr("错误: --path 需要一个目录参数\n")
return 1
else:
VLog.LogFmtS1("错误: 未知参数 %s\n", A)
PrintUsage()
return 1
i += 1
# --search-paths 是纯查询开关(只建表 + 打印,不读写缓存、不扫盘):未给 --cache 时放行。
# 其余校验一律不放宽——给了 --cache 仍按原样接受(该查询只是不使用它)。
if Cache_Path is None and Search_Paths_Flag == 0:
VLog.LogStr("错误: scan 模式必须提供 --cache <path>\n")
PrintUsage()
return 1
# 取舍:--need-confirm-timeout 单独出现时不报错(仅告警忽略)——它只是给
# --need-confirm 的从属参数,报错会因标志顺序/复用脚本而无谓失败;告警避免静默无效。
if Has_Timeout != 0 and Need_Confirm <= 0:
VLog.LogStr("警告: --need-confirm-timeout 未与 --need-confirm 一起使用,已忽略\n")
return 0
# ============================================================
# confirm-watch(内部隐藏子命令)参数解析
# 命令行形如:VET.exe confirm-watch --interval <秒> --timeout <毫秒> --parent-pid <pid>
# --ipc-write <句柄数值> [--verbose]
# --interval 必填且 >=1;--timeout 缺省 DEFAULT_CONFIRM_TIMEOUT_MS;--parent-pid 必填。
# --ipc-write 由父进程在建立 check 通道管道时传入(写端句柄的数值);缺省 0 = 无通道。
# --verbose 由父进程在自身为 verbose 时追加,使看门狗按同一规则输出细节。
# 该命令不出现在用法文本里,由父进程(scan + --need-confirm)自动拉起。
# ============================================================
def ParseWatchArgs() -> int:
global Watch_Interval, Watch_Timeout, Watch_ParentPid, Watch_IpcWrite, Watch_Verbose
Watch_Interval = 0
Watch_Timeout = -1
Watch_ParentPid = 0
Watch_IpcWrite = 0
Watch_Verbose = 0
Has_Pid: int = 0
i: int = 2
while i < sys._argc:
A: str = sys._argv[i]
if A is not None:
if string.strcmp(A, "--interval") == 0:
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")
return 1
Watch_Interval = S
i += 1
else:
VLog.LogStr("confirm-watch: --interval 需要一个秒数参数\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")
return 1
Watch_Timeout = T
i += 1
else:
VLog.LogStr("confirm-watch: --timeout 需要一个毫秒数参数\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")
return 1
Watch_ParentPid = P
Has_Pid = 1
i += 1
else:
VLog.LogStr("confirm-watch: --parent-pid 需要一个 pid 参数\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 需要一个十进制非负整数字面量句柄值,实际为: ",
sys._argv[i + 1])
VLog.LogStr("\n")
return 1
Watch_IpcWrite = U64_Val
i += 1
else:
VLog.LogStr("confirm-watch: --ipc-write 需要一个句柄数值参数\n")
return 1
elif string.strcmp(A, "--verbose") == 0:
Watch_Verbose = 1 # 无值开关:由父进程按自身 verbose 追加
else:
VLog.LogFmtS1("confirm-watch: 未知参数 %s\n", A)
return 1
i += 1
if Watch_Interval <= 0:
VLog.LogStr("confirm-watch: 缺少 --interval <秒>\n")
return 1
if Has_Pid == 0:
VLog.LogStr("confirm-watch: 缺少 --parent-pid <pid>\n")
return 1
if Watch_Timeout < 0:
Watch_Timeout = DEFAULT_CONFIRM_TIMEOUT_MS
return 0