461 lines
21 KiB
Plaintext
461 lines
21 KiB
Plaintext
import string
|
|
import sys
|
|
import lib.VLog as VLog
|
|
import lib.VDirs as VDirs
|
|
|
|
|
|
# VCli - command-line parsing for scan/ver (hand-written, no argparse).
|
|
# Exposes parsed flags via accessors consumed by VScan/VPe/App.main.vp.
|
|
|
|
define VET_VERSION = "0.2.0"
|
|
define SECS_MAX = 86400 # --need-confirm seconds upper bound (1 day; over = argument error)
|
|
define DEFAULT_SLEEP_MS = 10000 # --sleep default (ms)
|
|
define SLEEP_MAX_MS = 86400000 # --sleep ms upper bound (1 day; over = argument error)
|
|
define DEFAULT_CONFIRM_TIMEOUT_MS = 5000 # --need-confirm-timeout default (ms)
|
|
define CONFIRM_TIMEOUT_MAX_MS = 86400000 # --need-confirm-timeout ms upper bound (1 day)
|
|
# --pace <ms>: throttle sleep duration, range 0..60000; 0 = disable throttling (no Sleep).
|
|
# Upper bound is 1 minute (its purpose differs from --sleep's 1-day bound).
|
|
define DEFAULT_PACE_SLEEP_MS = 20 # --pace default (ms)
|
|
define PACE_SLEEP_MAX_MS = 60000 # --pace ms upper bound (1 minute)
|
|
# --pace-units <N>: file granularity, range 1..1000000 (a sleep point every N files; 1 = every file).
|
|
# 0 is meaningless and rejected along with invalid input; upper bound 1000000 is sparse enough.
|
|
define DEFAULT_PACE_FILE_UNITS = 64 # --pace-units default
|
|
define PACE_UNITS_MAX = 1000000 # --pace-units file-count upper bound
|
|
# --parent-pid upper bound (full DWORD range 4294967295).
|
|
# Use a wide global (longlong): a t.CDefine in u32 range materializes as i32 and truncates.
|
|
PID_MAX: longlong = 4294967295 # --parent-pid upper bound (full DWORD range)
|
|
|
|
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 = verbose logging
|
|
# Set by App/main.vp for the top-level form (VET.exe --search-paths ...): ParseScanArgs scans
|
|
# from argv[2], missing the switch at the subcommand slot; merged back as a flag after parsing.
|
|
Search_Paths_Implicit: int = 0
|
|
Search_Paths_Flag: int = 0 # --search-paths (scan); 1 = print searchable paths and exit
|
|
# --pace/--pace-units (scan): explicitly given => enable throttling (also under --once).
|
|
# Pace_Set records whether either was given; otherwise VScan keeps its own defaults.
|
|
Pace_Set: int = 0 # 1 = --pace or --pace-units given on the command line
|
|
Pace_Ms: int = DEFAULT_PACE_SLEEP_MS # --pace <ms> (0 = disable throttling)
|
|
Pace_Units: int = DEFAULT_PACE_FILE_UNITS # --pace-units <N> (sleep point every N files; 1 = every file)
|
|
# --first-no-pace (scan): disable throttling in round 1 only; later rounds follow normal rules.
|
|
# Does not change Pace_Set/Pace_Ms/Pace_Units; distinct from --pace 0 (no throttle in any round).
|
|
First_No_Pace_Flag: int = 0 # 1 = round 1 runs without throttling
|
|
|
|
# confirm-watch (hidden internal subcommand) params
|
|
Watch_Interval: int = 0 # seconds (passed by parent via --need-confirm)
|
|
Watch_Timeout: int = -1 # ms; -1 = not given (later set to DEFAULT_CONFIRM_TIMEOUT_MS)
|
|
Watch_ParentPid: longlong = 0 # parent pid (wide type: carries full DWORD, see PID_MAX)
|
|
Watch_IpcWrite: ulonglong = 0 # check-channel pipe write-end handle value (0 = not given => no channel)
|
|
Watch_Verbose: int = 0 # --verbose (appended by the parent when verbose)
|
|
|
|
# ParseUint64 output (module global, same convention as VPe.Pe_RawPtr)
|
|
U64_Val: ulonglong = 0
|
|
|
|
|
|
# Version exposed via a function to VPe/VScan (cross-module calls are supported; avoids relying
|
|
# on a cross-module string 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 values: when PaceSet()==0 the caller (VScan.RunScan) ignores the other two
|
|
# and keeps its own defaults; when ==1 see RunScan for the combined semantics.
|
|
def PaceSet() -> int:
|
|
return Pace_Set
|
|
|
|
|
|
def PaceSleepMs() -> int:
|
|
return Pace_Ms
|
|
|
|
|
|
def PaceFileUnits() -> int:
|
|
return Pace_Units
|
|
|
|
|
|
# --first-no-pace: 1 = round 1 runs without throttling (later rounds follow normal rules).
|
|
def FirstNoPace() -> int:
|
|
return First_No_Pace_Flag
|
|
|
|
|
|
# For the top-level form (VET.exe --search-paths ...); call before ParseScanArgs. Records the
|
|
# intent in Search_Paths_Implicit, merged into Search_Paths_Flag after parsing.
|
|
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 subcommand only: ver parses no switches (target is sys._argv[2]); --verbose anywhere in
|
|
# argv means verbose (scanned from index 2 like scan).
|
|
def HasVerbose() -> int:
|
|
for i in range(2, sys._argc):
|
|
A: str = sys._argv[i]
|
|
if A is not None:
|
|
if string.strcmp(A, "--verbose") == 0:
|
|
return 1
|
|
return 0
|
|
|
|
|
|
# Strictly parse a non-negative decimal integer: only '0'-'9' (empty/whitespace/sign/dot/others
|
|
# => -1). Max is longlong (signed 64-bit) so values above i32 range do not truncate.
|
|
def ParseUint(S: str, Max: longlong) -> longlong:
|
|
if S is None:
|
|
return -1
|
|
ub: bytes = bytes(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: # bounds short-circuit; also guards v*10 overflow
|
|
return -1
|
|
v = v * 10 + longlong(ch - 48)
|
|
if v > Max or v < 0: # out of range / 64-bit wrap (latter unreachable)
|
|
return -1
|
|
i += 1
|
|
return v
|
|
|
|
|
|
# --need-confirm seconds parse: equivalent to ParseUint(S, SECS_MAX), keeps error code -1
|
|
def ParseSecs(S: str) -> int:
|
|
return ParseUint(S, SECS_MAX)
|
|
|
|
|
|
# Strictly parse a non-negative decimal u64: only '0'-'9'; success returns 1 and stores U64_Val,
|
|
# failure returns 0. Parses the full u64 range because --ipc-write carries a sign-extended handle.
|
|
def ParseUint64(S: str) -> int:
|
|
global U64_Val
|
|
U64_Val = 0
|
|
if S is None:
|
|
return 0
|
|
ub: bytes = bytes(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): # times 10 would overflow
|
|
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("Usage:\n")
|
|
VLog.LogStr(" VET.exe ver <exe> [--verbose]\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(" 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] (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).
|
|
# Called by App/main.vp when dispatching scan (it needs NeedConfirm before 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:
|
|
# Top-level form (VET.exe --search-paths ...): the switch sits at argv[1] (subcommand
|
|
# slot), which this function skips; main.vp pre-sets it and it is merged in here.
|
|
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("Error: --cache requires a path argument\n")
|
|
return 1
|
|
elif string.strcmp(A, "--once") == 0:
|
|
Once_Flag = 1
|
|
elif string.strcmp(A, "--verbose") == 0:
|
|
Verbose_Flag = 1 # valueless switch: changes stderr verbosity only
|
|
elif string.strcmp(A, "--search-paths") == 0:
|
|
Search_Paths_Flag = 1 # valueless switch: print searchable paths and exit (no scan, no cache)
|
|
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("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()
|
|
return 1
|
|
Sleep_Ms = M
|
|
i += 1
|
|
else:
|
|
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("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()
|
|
return 1
|
|
Pace_Ms = P
|
|
Pace_Set = 1 # explicitly given => throttling on (also under --once)
|
|
i += 1
|
|
else:
|
|
VLog.LogStr("Error: --pace requires a millisecond argument\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 and invalid are both errors; 0 makes "a sleep point every 0 files" meaningless
|
|
if U <= 0:
|
|
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()
|
|
return 1
|
|
Pace_Units = U
|
|
Pace_Set = 1 # explicitly given => throttling on (also under --once)
|
|
i += 1
|
|
else:
|
|
VLog.LogStr("Error: --pace-units requires a file-count argument\n")
|
|
PrintUsage()
|
|
return 1
|
|
elif string.strcmp(A, "--first-no-pace") == 0:
|
|
# Valueless switch: requests round-1 no-throttle only; it neither changes throttle
|
|
# params nor sets Pace_Set (later rounds behave as if it were absent).
|
|
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:
|
|
# two fixed-arity calls on one line (LogFmt1 takes ints, LogFmtS1 takes strings)
|
|
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()
|
|
return 1
|
|
Need_Confirm = S
|
|
i += 1
|
|
else:
|
|
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("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()
|
|
return 1
|
|
Need_Confirm_Timeout = T
|
|
Has_Timeout = 1
|
|
i += 1
|
|
else:
|
|
VLog.LogStr("Error: --need-confirm-timeout requires a millisecond argument\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("Error: --path requires a directory argument\n")
|
|
return 1
|
|
else:
|
|
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("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("Warning: --need-confirm-timeout is used without --need-confirm, ignored\n")
|
|
return 0
|
|
|
|
|
|
# confirm-watch (hidden internal subcommand) argument parsing: e.g. VET.exe confirm-watch
|
|
# --interval <s> --timeout <ms> --parent-pid <pid> --ipc-write <handle> [--verbose]; not in usage.
|
|
|
|
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 requires an integer-literal second value >= 1\n")
|
|
return 1
|
|
Watch_Interval = S
|
|
i += 1
|
|
else:
|
|
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 requires a non-negative integer-literal millisecond value\n")
|
|
return 1
|
|
Watch_Timeout = T
|
|
i += 1
|
|
else:
|
|
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 requires a positive integer\n")
|
|
return 1
|
|
Watch_ParentPid = P
|
|
Has_Pid = 1
|
|
i += 1
|
|
else:
|
|
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 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 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: unknown argument %s\n", A)
|
|
return 1
|
|
i += 1
|
|
if Watch_Interval <= 0:
|
|
VLog.LogStr("confirm-watch: missing --interval <sec>\n")
|
|
return 1
|
|
if Has_Pid == 0:
|
|
VLog.LogStr("confirm-watch: missing --parent-pid <pid>\n")
|
|
return 1
|
|
if Watch_Timeout < 0:
|
|
Watch_Timeout = DEFAULT_CONFIRM_TIMEOUT_MS
|
|
return 0
|