Files
2026-10-17 21:57:25 +08:00

490 lines
24 KiB
Plaintext

import stdlib
import string
import viperlib
import memhub
import json
import lib.VFs as VFs
import lib.VLog as VLog
import lib.VPe as VPe
import lib.VProc as VProc
import lib.VGuid as VGuid
import lib.VCache as VCache
# ============================================================
# VCheck - execution + reply side of the check request channel (in the parent / scan process)
#
# Channel (attached to --need-confirm; present only while the watchdog runs):
# * A caller writes one line to stdin: {"check": "<exe path>", "id": <uint64>}
# * The watchdog parses it with the repo json library; a string "check" plus an integer "id" marks
# a check request, forwarded byte-for-byte (plus '\n') to the IPC pipe; it is not a heartbeat.
# * The parent polls that pipe without blocking (VGuard.PollIpc) and hands each line to this module.
# * This module runs the check and writes a single-line JSON reply to stdout:
# ok {"check_result": true, "id": <id>, "viper": {..}} (no check_msg)
# fail {"check_result": false, "id": <id>, "check_msg": "<reason>"}
# Serialized by the repo json library (compact single line, key order = insertion order).
#
# Check content (same rule as the scan candidate filter, single source):
# 1. PE/.PRVERSN via VPe.TryReadPeInfo (open errors split by VPe.PeOpenErr: NOT_FOUND / NO_PERMISSION)
# 2. --version via VProc.RunVersionProbe (timeout / launch error code / first 5 chars "Viper")
#
# Failure reasons are only the fixed ASCII strings below (case-sensitive): NOT_FOUND, NO_PERMISSION,
# NOT_EXE, NO_PRVERSN, PRVERSN_ERR, PREFIX_ERR, "EXE_ERR,0x<HEX>" (from VProc.ProbeLaunchErr),
# TIMEOUT, and CHANNEL_UNAVAILABLE (this module's buffers / reply pool unavailable, CheckInit failed;
# see EmitReplyUnavailable). Exact conditions are documented on the CK_* constants.
#
# check has priority over the scan: once a request is recognized this module runs the whole
# "read .PRVERSN -> run --version -> reply" synchronously; the tree scan stays at its VScan poll
# point until PollIpc returns. Single-threaded, so check and scan never interleave.
#
# Revalidation (VScan.RevalidateDiscovered) reuses VerifyExecutable (same rule); failures are removed.
#
# Memory ownership:
# * Buffers and the reply pool are created once in CheckInit (_mbuddy field buffers + a stdlib
# arena) and reused for the process lifetime; each reply resets Check_Pool first.
# * ParseCheckLine allocates its parse tree in a per-call stdlib arena and frees it before
# returning; it never aliases the cache table (same approach as VCache.ParseCacheText).
#
# Degradation (fail-safe, never silently disabled): if CheckInit fails, HandleCheckLine still
# recognizes the request and replies check_result=false + check_msg="CHANNEL_UNAVAILABLE" using a
# stdlib temp buffer (independent of _mbuddy/Check_Pool); VerifyExecutable returns 1 (skip
# revalidation, drop nothing) so a table is never wiped by an allocation failure.
#
# Injection: _mbuddy must be the same MemBuddy instance injected by App/main.vp; otherwise all
# allocations return None and CheckInit fails.
# ============================================================
# fixed failure reasons (all check_msg values; EXE_ERR is a prefix + hex code, see FormatExeErr)
define CK_NOT_FOUND = "NOT_FOUND"
define CK_NO_PERMISSION = "NO_PERMISSION"
define CK_NOT_EXE = "NOT_EXE"
define CK_NO_PRVERSN = "NO_PRVERSN"
define CK_PRVERSN_ERR = "PRVERSN_ERR"
define CK_PREFIX_ERR = "PREFIX_ERR" # --version launched but the first 5 chars are not Viper
define CK_TIMEOUT = "TIMEOUT"
# channel itself unavailable (CheckInit failed): the only degradation reason, see EmitReplyUnavailable
define CK_CHANNEL_UNAVAILABLE = "CHANNEL_UNAVAILABLE"
# source value when a check hit is cached (distinct from the scan side's "workspace")
define CK_SOURCE = "check"
# EXE_ERR reason buffer ("EXE_ERR,0x" + up to 8 hex digits + NUL)
define CK_MSG_CAP = 32
# Request-line parse limit, aligned with VGuard.GUARD_LINE_CAP (the watchdog already truncates or
# drops overlong lines; this is a second guard against parsing an abnormally long input).
define CK_LINE_MAX = 4096
# Parse arena size per input byte + fixed slack, same conservative rule as VCache.ParseCacheText
# (worst case ~1 JsonValue node + key/value copies + numeric temp per input byte; x128 covers
# 8-byte alignment waste). MemPool exhaustion calls exit(1), so AllocOk is pre-checked.
define CK_ARENA_PER_BYTE = 128
define CK_ARENA_SLACK = 4096
# Reply pool: 72 KiB. See CK_REPLY_NEED_FIXED for the per-reply allocation bound.
define CK_REPLY_POOL_CAP = 73728
# Fixed upper bound per reply on the reply pool: 9 JsonValue nodes (648), key-name copies incl. NUL
# (68), json.write output buffer (4096), integer serialization temp (32), plus 512 slack for NUL and
# alignment waste. Total 5356 => round up to 5632 (512-aligned).
define CK_REPLY_NEED_FIXED = 5632
# Upper bound for executable / displayName in a reply: both are converted through Check_Conv
# (VFs.CONV_CAP bytes), so their length is <= CONV_CAP-1 = 2047 (literal, per repo convention).
define CK_CONV_MAX = 2047
# Temp line buffer size for the degradation reply (EmitReplyUnavailable): stdlib.malloc, not
# _mbuddy (this path is exactly "mb buffers unavailable"). Upper bound incl. '\n' and NUL = 84 => 128.
define CK_FALLBACK_CAP = 128
# injected by App/main.vp (same MemBuddy instance as other modules)
_mbuddy: memhub.MemBuddy*
# one-time init state (Check_Tried ensures a failed init is not retried on every call)
Check_Tried: int = 0
Check_Ready: int = 0
Check_Handling: int = 0 # 1 = handling a check (re-entrancy guard; PollIpc is called from several scan points)
Check_Ver: bytes = None # .PRVERSN version string (VPe.VER_CAP)
Check_Arch: bytes = None # arch string (VPe.ARCH_CAP)
Check_Probe: bytes = None # --version capture buffer (VProc.VERSION_PROBE_DIAG_CAP)
Check_Msg: bytes = None # EXE_ERR reason buffer (CK_MSG_CAP)
Check_Path: bytes = None # exe path from the request (ANSI; VFs.PATH_BUF_CAP)
Check_Id: bytes = None # UUID of a new entry (VGuid.GUID_STR_CAP)
Check_Disp: bytes = None # "Viper " + version (VCache.DISP_CAP)
Check_Conv: bytes = None # ANSI -> UTF-8 conversion buffer (VFs.CONV_CAP)
Check_Arena: bytes = None # reply pool arena (stdlib.malloc, process-lifetime)
Check_Pool: memhub.MemPool* = None # reply pool (constructed in place on the heap, reset per reply)
# ParseCheckLine result (module globals, same convention as VPe.Pe_RawPtr)
Req_Id: longlong = 0 # request id (json int64 field; echoed back in the reply)
Req_PathOk: int = 0 # 1 = Check_Path filled (check can run); 0 = request recognized but path buffer unavailable
# ============================================================
# initialization
# ============================================================
# Request path buffer (shared by line parsing and CheckInit). The watchdog process does not run
# CheckInit but still parses request lines, so the buffer is allocated on first use by either side.
def EnsurePathBuf() -> int:
global Check_Path
if Check_Path is None:
Check_Path = _mbuddy.alloc(VFs.PATH_BUF_CAP)
if Check_Path is None:
return 0
return 1
# Allocate check buffers and the reply pool (idempotent; a failure warns once and marks Check_Ready
# unavailable). Fail-safe: check requests reply CHANNEL_UNAVAILABLE and revalidation is skipped
# (nothing is dropped) - never wipe the cache table due to an allocation failure. Warnings are
# grouped so the failing allocation set is identifiable.
def CheckInit() -> int:
global Check_Tried, Check_Ready
global Check_Ver, Check_Arch, Check_Probe, Check_Msg
global Check_Id, Check_Disp, Check_Conv, Check_Arena, Check_Pool
if Check_Tried != 0:
return Check_Ready
Check_Tried = 1
Check_Ver = _mbuddy.alloc(VPe.VER_CAP)
Check_Arch = _mbuddy.alloc(VPe.ARCH_CAP)
Check_Probe = _mbuddy.alloc(VProc.VERSION_PROBE_DIAG_CAP)
Check_Msg = _mbuddy.alloc(CK_MSG_CAP)
Check_Id = _mbuddy.alloc(VGuid.GUID_STR_CAP)
Check_Disp = _mbuddy.alloc(VCache.DISP_CAP)
Check_Conv = _mbuddy.alloc(VFs.CONV_CAP)
Check_Arena = stdlib.malloc(CK_REPLY_POOL_CAP)
if EnsurePathBuf() == 0:
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(" 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(" 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(" 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(" Warning: check reply pool construction failed, check channel unavailable\n")
return 0
Check_Pool = Pool
Check_Ready = 1
return 1
# ============================================================
# request-line parsing (shared rule for the watchdog classification and the parent execution)
# ============================================================
# Parse one text line with the repo json library and decide whether it is a check request.
# Returns 1 = yes (Req_Id set; when Req_PathOk=1, Check_Path holds the ANSI path), 0 = no.
# Rule (no string guessing): a top-level JSON object with a non-empty string "check" and an integer
# "id"; anything else (non-object, missing keys, wrong type, empty path, non-JSON, empty line) => 0.
# "id" must be numeric ("id":"1" is a string and does not count). Req_PathOk=0 means "request valid
# but path buffer unavailable"; still returns 1 so the caller replies CHANNEL_UNAVAILABLE and the
# watchdog forwards it, never mistaking a check request for heartbeat input. Single pass over the
# top-level children; key order does not matter.
def ParseCheckLine(Txt: str) -> int:
global Req_Id, Req_PathOk
Req_Id = 0
Req_PathOk = 0
if Txt is None:
return 0
L: sizet = string.strlen(Txt)
if L == 0 or L > sizet(CK_LINE_MAX):
return 0
Cap: sizet = L * sizet(CK_ARENA_PER_BYTE) + sizet(CK_ARENA_SLACK)
Arena: bytes = stdlib.malloc(Cap)
if Arena is None:
return 0
Pool: memhub.MemPool* = memhub.MemPool(Arena, Cap)
if Pool is None:
stdlib.free(rawptr(Arena))
return 0
# Pool capacity pre-check (fail-closed): the parse tree must be known to fit, otherwise
# json.parse's pool.alloc reaches printf + exit(1). Need and Cap share the same derivation, so
# AllocOk(Cap) always holds; this only turns a shrunk derivation into a controlled degradation.
if Pool.AllocOk(Cap) == 0:
stdlib.free(rawptr(Arena))
return 0
Root: json.JsonValue* = json.parse(Pool, Txt)
HasCk: int = 0
HasId: int = 0
if Root != None and Root.vtype == json.JSON_OBJECT:
cu: json.JsonValue* = Root.child
while cu != None:
if cu.key != None:
if string.strcmp(cu.key, "check") == 0:
if cu.vtype == json.JSON_STRING and cu.str_val != None:
if string.strlen(cu.str_val) > 0:
HasCk = 1
# Console/command-line input arrives as UTF-8; the A-series API needs ANSI.
# Utf8ToAnsi copies invalid UTF-8 as-is, so plain ANSI input also works.
# If the path buffer is unavailable, Req_PathOk stays 0 but it is still a check.
if EnsurePathBuf() != 0:
VFs.Utf8ToAnsi(cu.str_val, Check_Path, VFs.PATH_BUF_CAP)
Req_PathOk = 1
elif string.strcmp(cu.key, "id") == 0:
if cu.vtype == json.JSON_INT:
Req_Id = longlong(cu.int_val)
HasId = 1
cu = cu.next
stdlib.free(rawptr(Arena))
if HasCk != 0 and HasId != 0:
return 1
return 0
# ParseCheckLine exposes its result through module globals: Check_Path (ANSI path), Req_Id and
# Req_PathOk (same convention as VPe.Pe_RawPtr / VFs.LastReadSize; read directly within the module).
# ============================================================
# failure reason mapping
# ============================================================
# Map a PE parse failure code (VPe.TryReadPeInfo rc + ParsePeHead prc) to a fixed reason string.
# rc 1 (CreateFileA failed) splits by OpenErr: 2/3 = NOT_FOUND, else NO_PERMISSION. rc 2..5 => NOT_EXE;
# rc 6/7 => PRVERSN_ERR; rc 5/107 => NO_PRVERSN. prc (rc-100): 7 = NO_PRVERSN, 6 = PRVERSN_ERR,
# others = NOT_EXE.
def MapPeFailure(Rc: int, OpenErr: ulong) -> str:
if Rc == 1:
if OpenErr == 2 or OpenErr == 3:
return CK_NOT_FOUND
return CK_NO_PERMISSION
if Rc == 5 or Rc == 107:
return CK_NO_PRVERSN
if Rc == 6 or Rc == 7:
return CK_PRVERSN_ERR
if Rc >= 100:
prc: int = Rc - 100
if prc == 7:
return CK_NO_PRVERSN
if prc == 6:
return CK_PRVERSN_ERR
return CK_NOT_EXE
return CK_NOT_EXE
# Format a launch-failure code as `EXE_ERR,0x<UPPERHEX>` (writes Check_Msg, returns it), e.g.
# 2 -> "EXE_ERR,0x2", 193 -> "EXE_ERR,0xC1". The result points to a module buffer and must be used
# immediately (EmitReplyFail copies it into the reply pool).
def FormatExeErr(Err: ulong) -> str:
viperlib.snprintf(Check_Msg, sizet(CK_MSG_CAP), "EXE_ERR,0x%X", Err)
return str(Check_Msg)
# ============================================================
# check execution
# ============================================================
# Full check of one exe (PE/.PRVERSN + --version). On success returns None and fills Check_Ver /
# Check_Arch and Check_Disp ("Viper " + version); on failure returns a fixed reason string (a CK_*
# constant or FormatExeErr's shared buffer). This function does no cache write-back.
def CheckExecutable(PathAnsi: str) -> str:
Check_Ver[0] = 0
Check_Arch[0] = 0
# (1) PE / .PRVERSN (static source of version and arch)
rc: int = VPe.TryReadPeInfo(PathAnsi, Check_Ver, VPe.VER_CAP, Check_Arch, VPe.ARCH_CAP)
if rc != 0:
return MapPeFailure(rc, VPe.PeOpenErr())
if string.strlen(str(Check_Ver)) == 0:
return CK_PRVERSN_ERR # section present but empty (first byte is NUL)
# (2) --version probe passes only if the process ends normally and the first 5 chars are "Viper"
pc: int = VProc.RunVersionProbe(PathAnsi, Check_Probe,
sizet(VProc.VERSION_PROBE_DIAG_CAP), 0)
if pc == 2 or VProc.Probe_TimedOut != 0:
return CK_TIMEOUT
if pc != 0:
# launch failed (args/command buffer/pipe/CreateProcessA): report EXE_ERR,0x<code>
return FormatExeErr(VProc.ProbeLaunchErr())
if VProc.OutputHasViperPrefix(str(Check_Probe)) == 0:
# launched but the first 5 chars are not Viper => distinct reason PREFIX_ERR
return CK_PREFIX_ERR
viperlib.snprintf(Check_Disp, sizet(VCache.DISP_CAP), "Viper %s", str(Check_Ver))
return None
# Full revalidation of a discovered item: file exists + PE/.PRVERSN parseable + --version first 5
# chars are Viper (all CheckExecutable conditions, without needing version/arch). Returns 1 = keep,
# 0 = drop. Fail-safe: if CheckInit failed, returns 1 (drop nothing) so the table is never wiped;
# the warning is emitted once by CheckInit (plus a scan-start note from VScan.ScanInit).
def VerifyExecutable(PathAnsi: str) -> int:
if CheckInit() == 0:
return 1
if CheckExecutable(PathAnsi) == None:
return 1
return 0
# ============================================================
# cache insert (check hit)
# ============================================================
# Insert a check hit into the cache (if it already exists, skip without rewriting its id/fields).
# Returns 1 = the hit is in the cache (newly written or already present), 0 = cache unavailable.
# Idempotent: CacheFind matches by exact ANSI path, so repeated checks do not reinsert or refresh.
def TrackChecked(PathAnsi: str) -> int:
if VCache.CacheFind(PathAnsi) >= 0:
return 1
if VCache.CacheAdd(PathAnsi, str(Check_Id), str(Check_Ver), str(Check_Arch),
CK_SOURCE, str(Check_Disp)) == 0:
VLog.LogStr(" Warning: check hit but cache insert failed (capacity full or allocation failed), entry not tracked this round\n")
return 0
return 1
# ============================================================
# reply (single-line JSON on stdout)
# ============================================================
# Write one stdout line (with trailing '\n', in a single WriteFile). json.write's output buffer is
# 4096, far larger than this text, so there is room after the NUL for '\n' and a new NUL; a single
# write keeps a reply line from interleaving with other writers.
def WriteReplyLine(Txt: str) -> int:
n: sizet = string.strlen(Txt)
if n + 2 <= sizet(4096):
Txt[n] = '\n'
Txt[n + 1] = 0
VLog.StdoutWrite(Txt)
return 1
# unreachable (the length guard keeps the text far below 4096): fallback to avoid a newline-less line
VLog.StdoutWrite(Txt)
VLog.StdoutWrite("\n")
return 1
# Success reply: {"check_result": true, "id": <id>, "viper": {<found item>}}. The viper dict has the
# same shape/order as existing found items (id/executable/version/arch/source/displayName); source
# is "check". A cached exe reuses its cache fields (id stable, nothing rewritten).
def EmitReplyOk(ReqIdVal: longlong, PathAnsi: str):
Id: str = str(Check_Id)
Ver: str = str(Check_Ver)
Arch: str = str(Check_Arch)
Disp: str = str(Check_Disp)
idx: int = VCache.CacheFind(PathAnsi)
if idx >= 0:
# already cached: do not change its id/fields; reply with the cached record (stable id)
Id = VCache.CacheId(idx)
Ver = VCache.CacheVer(idx)
Arch = VCache.CacheArch(idx)
Disp = VCache.CacheDisp(idx)
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(" Warning: CoCreateGuid failed, id of the check cache entry is empty\n")
Check_Id[0] = 0
Id = str(Check_Id)
TrackChecked(PathAnsi)
Check_Pool.reset()
ValNeed: sizet = sizet(CK_CONV_MAX) + sizet(CK_CONV_MAX)
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(" 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(" 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(" 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)
if VFs.AnsiToUtf8(PathAnsi, Check_Conv, VFs.CONV_CAP) == 0:
VFs.CopyRaw(Check_Conv, VFs.CONV_CAP, PathAnsi)
json.object_set(Check_Pool, Sub, "executable", json.string_val(Check_Pool, str(Check_Conv)))
json.object_set(Check_Pool, Sub, "version", json.string_val(Check_Pool, Ver))
json.object_set(Check_Pool, Sub, "arch", json.string_val(Check_Pool, Arch))
json.object_set(Check_Pool, Sub, "source", json.string_val(Check_Pool, CK_SOURCE))
if VFs.AnsiToUtf8(Disp, Check_Conv, VFs.CONV_CAP) == 0:
VFs.CopyRaw(Check_Conv, VFs.CONV_CAP, Disp)
json.object_set(Check_Pool, Sub, "displayName", json.string_val(Check_Pool, str(Check_Conv)))
json.object_set(Check_Pool, Obj, "viper", Sub)
Txt: str = json.write(Check_Pool, Obj, False)
if Txt == None:
VLog.LogStr(" Warning: check reply serialization failed (pool insufficient), not replied\n")
return
WriteReplyLine(Txt)
# Failure reply: {"check_result": false, "id": <id>, "check_msg": "<reason>"}
def EmitReplyFail(ReqIdVal: longlong, Msg: str):
Check_Pool.reset()
Need: sizet = sizet(CK_REPLY_NEED_FIXED)
if Msg != None:
Need = Need + string.strlen(Msg)
if Check_Pool.AllocOk(Need) == 0:
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(" 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(" Warning: check reply serialization failed (pool insufficient), not replied\n")
return
WriteReplyLine(Txt)
# Degradation reply (compact single line): {"check_result": false, "id": <id>,
# "check_msg": "CHANNEL_UNAVAILABLE"}. Used only when CheckInit failed, so it must not depend on
# _mbuddy / Check_Pool: the line buffer comes from stdlib.malloc (freed immediately), and the body
# is produced by viperlib.snprintf with a fixed template matching the library's compact form (no
# escaping needed). This keeps "request received but channel unavailable" visible. Returns 1 =
# replied, 0 = even the temp buffer is unavailable.
def EmitReplyUnavailable(ReqIdVal: longlong) -> int:
Fb: bytes = stdlib.malloc(CK_FALLBACK_CAP)
if Fb is None:
return 0
viperlib.snprintf(Fb, sizet(CK_FALLBACK_CAP),
"{\"check_result\":false,\"id\":%lld,\"check_msg\":\"%s\"}\n",
int64(ReqIdVal), CK_CHANNEL_UNAVAILABLE)
VLog.StdoutWrite(str(Fb))
stdlib.free(rawptr(Fb))
return 1
# ============================================================
# full handling of one request line (called by VGuard.PollIpc at a non-blocking poll point)
# ============================================================
# Handle one check request line: parse -> check -> reply. Returns 1 = handled (replied regardless
# of success/failure), 0 = not a check request / not handled. Re-entrancy: returns 0 while
# Check_Handling is 1. Degradation: if CheckInit failed, still recognize the request and reply
# CHANNEL_UNAVAILABLE rather than silently dropping it.
def HandleCheckLine(Txt: str) -> int:
global Check_Handling
if Check_Handling != 0:
return 0
if CheckInit() == 0:
if ParseCheckLine(Txt) == 0:
return 0
EmitReplyUnavailable(Req_Id)
return 1
if ParseCheckLine(Txt) == 0:
return 0
Check_Handling = 1
Path: str = str(Check_Path)
Reason: str = CheckExecutable(Path)
if Reason == None:
EmitReplyOk(Req_Id, Path)
else:
EmitReplyFail(Req_Id, Reason)
Check_Handling = 0
return 1