Newer
Older
# Required privileges: ALTPRI, SYSNAM, PRMMBX, IMPERSONATE, SETPRV, WORLD
import argparse
import configparser
import ctypes
import datetime
import json
import logging
import logging.handlers
import queue
from dataclasses import dataclass, field
from enum import IntEnum
from typing import Any, Dict, List, Literal, Tuple, Type, NewType
from ovms import (
accdef,
cmbdef,
crtl,
dvidef,
iodef,
iosbdef,
jpidef,
lnmdef,
mbxqio,
prvdef,
psldef,
ssdef,
starlet,
stsdef,
)
from ovms import ast as vmsast
from ovms.rtl import lib
logger: logging.Logger
logger = logging.getLogger('supervisord')
supervisord_table_name = b'SUPERVISORD_TABLE'
current_tick: int = 0
timer_queue: queue.PriorityQueue['PrioritizedItem'] = queue.PriorityQueue()
supervisorctl_pwd = ''
TRUTHY_STRINGS = ('yes', 'true', 'on', '1')
FALSY_STRINGS = ('no', 'false', 'off', '0')
timer_delay = starlet.bintim('0 0:00:01.00')[1]
timer_astctx = vmsast.AstContext(vmsast.M_WAKE | vmsast.M_QUEUE)
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# Define a new type called "PrvMask", which is internally an `int`
PrvMask = NewType('PrvMask', int)
# Ditto for "PidType"
PidType = NewType('PidType', int)
privileges: Dict[str, PrvMask] = {
'NOSAME': PrvMask(0),
'ACNT': PrvMask(prvdef.PRV_M_ACNT),
'ALLSPOOL': PrvMask(prvdef.PRV_M_ALLSPOOL),
'ALTPRI': PrvMask(prvdef.PRV_M_ALTPRI),
'AUDIT': PrvMask(prvdef.PRV_M_AUDIT),
'BUGCHK': PrvMask(prvdef.PRV_M_BUGCHK),
'BYPASS': PrvMask(prvdef.PRV_M_BYPASS),
'CMEXEC': PrvMask(prvdef.PRV_M_CMEXEC),
'CMKRNL': PrvMask(prvdef.PRV_M_CMKRNL),
'DIAGNOSE': PrvMask(prvdef.PRV_M_DIAGNOSE),
'DOWNGRADE': PrvMask(prvdef.PRV_M_DOWNGRADE),
'EXQUOTA': PrvMask(prvdef.PRV_M_EXQUOTA),
'GROUP': PrvMask(prvdef.PRV_M_GROUP),
'GRPNAM': PrvMask(prvdef.PRV_M_GRPNAM),
'GRPPRV': PrvMask(prvdef.PRV_M_GRPPRV),
'IMPERSONATE': PrvMask(prvdef.PRV_M_IMPERSONATE),
'IMPORT': PrvMask(prvdef.PRV_M_IMPORT),
'LOG_IO': PrvMask(prvdef.PRV_M_LOG_IO),
'MOUNT': PrvMask(prvdef.PRV_M_MOUNT),
'NETMBX': PrvMask(prvdef.PRV_M_NETMBX),
'OPER': PrvMask(prvdef.PRV_M_OPER),
'PFNMAP': PrvMask(prvdef.PRV_M_PFNMAP),
'PHY_IO': PrvMask(prvdef.PRV_M_PHY_IO),
'PRMCEB': PrvMask(prvdef.PRV_M_PRMCEB),
'PRMGBL': PrvMask(prvdef.PRV_M_SYSGBL),
'PRMMBX': PrvMask(prvdef.PRV_M_PRMMBX),
'PSWAPM': PrvMask(prvdef.PRV_M_PSWAPM),
'READALL': PrvMask(prvdef.PRV_M_READALL),
'SECURITY': PrvMask(prvdef.PRV_M_SECURITY),
'SETPRV': PrvMask(prvdef.PRV_M_SETPRV),
'SHARE': PrvMask(prvdef.PRV_M_SHARE),
'SHMEM': PrvMask(prvdef.PRV_M_SHMEM),
'SYSGBL': PrvMask(prvdef.PRV_M_SYSGBL),
'SYSLCK': PrvMask(prvdef.PRV_M_SYSLCK),
'SYSNAM': PrvMask(prvdef.PRV_M_SYSNAM),
'SYSPRV': PrvMask(prvdef.PRV_M_SYSPRV),
'TMPMBX': PrvMask(prvdef.PRV_M_TMPMBX),
'UPGRADE': PrvMask(prvdef.PRV_M_UPGRADE),
'VOLPRO': PrvMask(prvdef.PRV_M_VOLPRO),
'WORLD': PrvMask(prvdef.PRV_M_WORLD),
}
quotas: Dict[str, int] = {
'ASTLM': pqldef.PQL__ASTLM,
'BIOLM': pqldef.PQL__BIOLM,
'BYTLM': pqldef.PQL__BYTLM,
'CPULM': pqldef.PQL__CPULM,
'DIOLM': pqldef.PQL__DIOLM,
'ENQLM': pqldef.PQL__ENQLM,
'FILLM': pqldef.PQL__FILLM,
'JTQUOTA': pqldef.PQL__JTQUOTA,
'PGFLQUOTA': pqldef.PQL__PGFLQUOTA,
'PRCLM': pqldef.PQL__PRCLM,
'TQELM': pqldef.PQL__TQELM,
'WSDEFAULT': pqldef.PQL__WSDEFAULT,
'WSEXTENT': pqldef.PQL__WSEXTENT,
'WSQUOTA': pqldef.PQL__WSQUOTA,
}
def start_timer():
global timer_delay, timer_astctx
timer_astctx.reset()
starlet.setimr(daytim=timer_delay, reqidt=timer_astctx)
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
class RestartWhenExitUnexpected:
pass
class RestartUnconditionally:
pass
def auto_restart(
value: str,
) -> (
Type[RestartUnconditionally]
| Type[RestartWhenExitUnexpected]
| Literal[False]
):
value = str(value.lower())
computed_value = value
if value in TRUTHY_STRINGS:
computed_value = RestartUnconditionally
elif value in FALSY_STRINGS:
computed_value = False
elif value == 'unexpected':
computed_value = RestartWhenExitUnexpected
if computed_value not in (
RestartWhenExitUnexpected,
RestartUnconditionally,
False,
):
raise ValueError("invalid 'autorestart' value %r" % value)
return computed_value
class TimerItemType(IntEnum):
UNDEF = 0
PROC_STARTING = 1
PROC_BACKOFF = 2
PROC_STOPPING = 3
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
class TimerItem(object):
typ: TimerItemType
value: Any
cancel: bool
done: bool
def __init__(
self,
typ: TimerItemType = TimerItemType.UNDEF,
value: Any = None,
) -> None:
self.typ = typ
self.value = value
self.cancel = False
self.done = False
@dataclass(order=True)
class PrioritizedItem:
priority: int
item: TimerItem = field(compare=False)
class AstParamType(IntEnum):
PROCTERM = 1
CMD = 2
TIMER = 3
# http://supervisord.org/subprocess.html#process-states
class ProcessStates(IntEnum):
STOPPED = 0
STARTING = 10
RUNNING = 20
BACKOFF = 30
STOPPING = 40
EXITED = 100
FATAL = 200
UNKNOWN = 1000
class ProcessInfo(object):
state: ProcessStates
finalsts: int | None
start_time: datetime.datetime | None
end_time: datetime.datetime | None
def __init__(
self,
start_time: datetime.datetime | None = None,
state: ProcessStates = ProcessStates.STOPPED,
) -> None:
self.pid = pid
self.start_time = start_time
self.end_time = None
self.finalsts = None
self.state = state
class Program(object):
mbxunt = 0
name: str
image: bytes
stdout_logfile: bytes
stderr_logfile: bytes
startsecs: int
startretries: int
stopwaitsecs: int
baspri: int
nouaf: bool
quotas: List[Tuple[int, int]] | None
prv: PrvMask | None
running_processes: Dict[PidType, 'Program']
running_processes = dict()
programs: Dict[str, 'Program']
programs = dict()
remain_startretries: int
timer_item = TimerItem | None
kill_request: bool
autorestart: (
Type[RestartUnconditionally]
| Type[RestartWhenExitUnexpected]
| Literal[False]
)
exitcodes: List[int]
def __init__(
self,
name: str,
user: str,
command: str,
image: str,
stdout_file: str,
stderr_file: str,
startsecs: int,
startretries: int,
stopwaitsecs: int,
nouaf: bool,
quotas: List[Tuple[int, int]] | None,
prv: PrvMask | None,
kt_limit: int,
baspri: int,
autorestart: (
Type[RestartUnconditionally]
| Type[RestartWhenExitUnexpected]
| Literal[False]
),
exitcodes: List[int],
self.image = image.encode()
self.command = command.encode()
self.process_name = process_name.encode()
self.autostart = autostart
self.stdout_file = stdout_file.encode()
self.stderr_file = stderr_file.encode()
self.startsecs = startsecs
self.startretries = startretries
self.remain_startretries = startretries + 1
self.stopwaitsecs = stopwaitsecs
self.timer_item = None
self.kill_request = False
self.autorestart = autorestart
self.nouaf = nouaf
self.quotas = quotas
self.prv = prv
self.kt_limit = kt_limit
self.baspri = baspri
self.exitcodes = exitcodes
def process_exists(self) -> bool:
return self.process.pid in self.running_processes
def process_is_backoff(self) -> bool:
return self.process.state == ProcessStates.BACKOFF
def process_is_fatal(self) -> bool:
return self.process.state == ProcessStates.FATAL
def process_is_stopped(self) -> bool:
return self.process.state == ProcessStates.STOPPED
def process_is_stopping(self) -> bool:
return self.process.state == ProcessStates.STOPPING
def set_running(self):
self.process.state = ProcessStates.RUNNING
self.remain_startretries = self.startretries + 1
# def create_process(self, check_finalsts: bool = True):
def create_process(self, from_terminated: bool):
if from_terminated:
# program has already been started
if not self.autorestart and self.process.finalsts is not None:
return
if (
self.autorestart is RestartWhenExitUnexpected
and self.process.finalsts in self.exitcodes
):
return
if self.process_exists():
try:
v = lib.get_logical(
self.process_name + b'_PID',
supervisord_table_name,
)[1]
while True:
try:
itime, stime = lib.getjpi(jpidef.JPI__LOGINTIM, pid)[2:]
break
except OSError as e:
if e.errno != ssdef.SS__SUSPENDED:
raise
itime = crtl.fix_time(itime)
assert itime is not None
self.process.start_time = datetime.datetime.fromtimestamp(itime)
self.set_running()
Program.running_processes[pid] = self
return
except OSError as e:
if e.errno not in (ssdef.SS__NOLOGNAM, ssdef.SS__NONEXPR):
raise
if (
self.process.state == ProcessStates.BACKOFF
and self.timer_item
and not self.timer_item.done
):
return
self.kill_request = False
if self.startretries > 0:
if self.remain_startretries == 0:
self.process.state = ProcessStates.FATAL
return
self.remain_startretries -= 1
if self.timer_item:
self.timer_item.cancel = True
self.timer_item = None
self.process.finalsts = None
# If the image argument specifies the SYS$SYSTEM:LOGINOUT.EXE,
# the UIC of the created process will be the UIC of the caller of $CREPRC,
# and the UIC parameter is ignored.
# So, we need to use persona
userpro = starlet.create_user_profile(usrnam=self.user)[1]
persona_id = starlet.persona_create(usrpro=userpro)[1]
persona_previous_id = starlet.persona_assume(persona_id)[1]
# Create a detach process (stsflg=prvdef.PRV_M_IMPERSONATE)
# From John Gillings:
# (https://community.hpe.com/t5/operating-system-openvms/run-uic-vs-sys-creprc/td-p/5171510)
# General comment about PRC$M_NOUAF ...
# When you run LOGINOUT in a detached process *WITHOUT* specifying PRC$M_NOUAF
# for $CREPRC, or *WITH* /AUTHORIZE on the DCL RUN command, (note that the
# default is reversed between the two), many of the process attributes you have
# specified with qualifiers or parameters may be overridden in the resulting
# process.
# That includes UIC, process name, quotas and privileges. Behind the scenes,
# the process is created with whatever you specified, but LOGINOUT replaces
# them with values from the UAF.
#
# From https://docs.vmssoftware.com/vsi-openvms-programming-concepts-manual-volume-i/
# The SYS$CREPRC system service also does not provide default equivalence names
# for the logical names SYS$LOGIN, SYS$LOGIN_DEVICE, and SYS$SCRATCH.
# These logical names are available to the created process only when the
# specified image is LOGINOUT, and when the PRC$M_NOUAF flag is not set.
try:
stsflg = (
prcdef.PRC_M_IMPERSONATE
| prcdef.PRC_M_PARSE_EXTENDED
| prcdef.PRC_M_KT_LIMIT
)
if self.nouaf:
stsflg |= prcdef.PRC_M_NOUAF
pid = PidType(
starlet.creprc(
image=self.image,
input=self.command,
output=self.stdout_file,
error=self.stderr_file,
prcnam=self.process_name,
mbxunt=Program.mbxunt,
quota=self.quotas,
prv=self.prv,
kt_limit=self.kt_limit,
baspri=self.baspri,
# prcdef.PRC_M_DETACH and prcdef.PRC_M_IMPERSONATE are synonyms
stsflg=stsflg,
)[1]
)
except OSError as e:
self.process.state = ProcessStates.FATAL
return
finally:
starlet.persona_assume(persona_previous_id)
starlet.persona_delete(persona_id)
if error is not None:
logger.warning(
f"Can' create process {self.process_name}, error {error}"
lib.set_logical(
self.process_name + b'_PID',
hex(pid)[2:].upper(),
supervisord_table_name,
)
lib.set_logical(
self.process_name + b'_RUN',
b'1',
supervisord_table_name,
)
self.process.start_time = datetime.datetime.now()
if self.startsecs == 0:
self.set_running()
else:
self.process.state = ProcessStates.STARTING
self.timer_item = TimerItem(TimerItemType.PROC_STARTING, self)
timer_queue.put(
PrioritizedItem(current_tick + self.startsecs, self.timer_item)
)
Program.running_processes[PidType(pid)] = self
lib.set_logical(
self.process_name + b'_BEG',
str(self.process.start_time)[:19],
supervisord_table_name,
)
try:
lib.delete_logical(
self.process_name + b'_END',
supervisord_table_name,
)
except OSError:
pass
logging.info(
f'Process {self.process_name.decode()} created {hex(pid)[2:].upper()}'
)
def kill(self):
if (
self.process_is_stopped()
or self.process_is_stopping()
or self.process_is_fatal()
):
return
if self.process_is_backoff() or self.process_is_fatal():
self.process.state = ProcessStates.STOPPED
self.remain_startretries = self.startretries + 1
if self.timer_item:
self.timer_item.cancel = True
self.timer_item = None
return
if not self.process_exists() or self.kill_request:
return
self.kill_request = True
pid = self.process.pid
if self.process.state == ProcessStates.STOPPING:
starlet.delprc(self.process.pid)
else:
starlet.forcex(pid, code=ssdef.SS__FORCEX)
if self.timer_item:
self.timer_item.cancel = True
self.timer_item = TimerItem(TimerItemType.PROC_STOPPING, self)
timer_queue.put(
PrioritizedItem(current_tick + self.stopwaitsecs, self.timer_item)
)
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
def set_terminated(self, finalsts: int, end_time: datetime.datetime):
global supervisord_table_name
assert self.process is not None
pid = self.process.pid
finalsts = finalsts & 0xEFFFFFFF
self.process.finalsts = finalsts
self.process.end_time = end_time
del Program.running_processes[pid]
if self.timer_item:
self.timer_item.cancel = True
self.timer_item = None
lib.set_logical(
self.process_name + b'_RUN',
b'0',
supervisord_table_name,
)
lib.set_logical(
self.process_name + b'_END',
str(end_time)[:22],
supervisord_table_name,
)
if self.process.state == ProcessStates.STARTING:
self.process.state = ProcessStates.BACKOFF
self.timer_item = TimerItem(TimerItemType.PROC_BACKOFF, self)
timer_queue.put(
PrioritizedItem(
5
+ current_tick
+ self.startretries
- self.remain_startretries
+ 1,
self.timer_item,
)
)
else:
self.process.state = (
ProcessStates.STOPPED
if self.kill_request
else ProcessStates.EXITED
)
def logicals_init():
global supervisord_table_name
attr=lnmdef.LNM_M_CREATE_IF,
promsk=0xE000,
tabnam=supervisord_table_name,
partab=b'LNM$SYSTEM_DIRECTORY',
acmode=psldef.PSL_C_SUPER,
)
lib.set_logical(
b'LNM$PERMANENT_MAILBOX',
supervisord_table_name,
b'LNM$PROCESS_DIRECTORY',
)
def mbx_init() -> Tuple[int, int, int]:
global logger
logger.info('Creating mailbox')
s, chan = starlet.crembx(
prmflg=1,
lognam='SUPERVISORD_MBX',
maxmsg=accdef.ACC_K_TERMLEN,
bufquo=1024 * 64,
promsk=0x0000FF00,
acmode=psldef.PSL_C_USER,
flags=cmbdef.CMB_M_READONLY,
)
r = lib.getdvi(dvidef.DVI__UNIT, chan)
mbxunt = r[1]
s, chancmd = starlet.crembx(
prmflg=1,
lognam='SUPERVISORD_CMD',
maxmsg=2048,
bufquo=8192,
promsk=0x0000FF00,
acmode=psldef.PSL_C_USER,
flags=cmbdef.CMB_M_READONLY,
)
logger.info(f'Mailbox created {mbxunt}')
return chan, mbxunt, chancmd # , chancmd_r
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
class AstParam(object):
ptype: AstParamType
iosb: iosbdef.IOSB_r_io_64 | None
buff: ctypes.Array[ctypes.c_char] | None
def __init__(
self,
ptype: AstParamType,
iosb: iosbdef.IOSB_r_io_64 | None = None,
buff: ctypes.Array[ctypes.c_char] | None = None,
):
self.ptype = ptype
self.iosb = iosb
self.buff = buff
def qio_procterm(fterm: mbxqio.MBXQIO) -> vmsast.AstContext:
astctxt = vmsast.AstContext(flags=vmsast.M_QUEUE | vmsast.M_WAKE)
iosb, tbuff = fterm.read_nowait(accdef.ACC_K_TERMLEN, astctxt)
astctxt.param = AstParam(AstParamType.PROCTERM, iosb, tbuff)
return astctxt
def qio_cmd(fcmd: mbxqio.MBXQIO) -> vmsast.AstContext:
astctxt = vmsast.AstContext(flags=vmsast.M_QUEUE | vmsast.M_WAKE)
iosb, cbuff = fcmd.read_nowait(256, astctxt)
astctxt.param = AstParam(AstParamType.CMD, iosb, cbuff)
return astctxt
def send_cmd_reply(mbxreply_name: str, cmdreply: dict):
with mbxqio.MBXQIO(mbxname=mbxreply_name) as fcmd_r:
jscmd = json.dumps(cmdreply)
try:
fcmd_r.write(
jscmd.encode('ascii'),
iodef.IO_M_READERCHECK | iodef.IO_M_NOW,
)
except OSError:
pass
def dispatch_cmd(res: bytes):
global supervisorctl_pwd
pwd = jscmd['pwd']
jscmd['pwd'] = '*****'
fcmd_r = jscmd['mbxreply']
if pwd != supervisorctl_pwd:
logging.info('Invalid password')
send_cmd_reply(fcmd_r, {'error': 'Invalid password'})
return
send_cmd_reply(fcmd_r, {'result': None})
exit(1)
case 'start':
pgmnames = set([name.upper() for name in jscmd['programs']])
if 'ALL' in pgmnames:
for pgm in Program.programs.values():
if pgm.process.state == ProcessStates.FATAL:
pgm.process.state = ProcessStates.STOPPED
pgm.remain_startretries = pgm.startretries + 1
pgm.create_process(False)
else:
for pgm in Program.programs.values():
if pgm.name in pgmnames:
if pgm.process.state == ProcessStates.FATAL:
pgm.process.state = ProcessStates.STOPPED
pgm.remain_startretries = pgm.startretries + 1
pgm.create_process(False)
case 'stop':
pgmnames = set([name.upper() for name in jscmd['programs']])
if 'ALL' in pgmnames:
for pgm in Program.programs.values():
pgm.kill()
else:
for pgm in Program.programs.values():
if pgm.name in pgmnames:
pgm.kill()
case 'status':
pgmnames = set([name.upper() for name in jscmd['programs']])
lst = []
for pgm in Program.programs.values():
if pgmnames and pgm.name not in pgmnames:
continue
lst.append(
[
pgm.process_name.decode('ascii'),
hex(pgm.process.pid)[2:].upper(),
str(pgm.process.start_time)[:19],
pgm.process.state,
]
)
if pgm.process.state in (
ProcessStates.BACKOFF,
ProcessStates.STOPPED,
ProcessStates.EXITED,
ProcessStates.FATAL,
):
lst[-1].append(str(pgm.process.end_time)[:19])
lst[-1].append(pgm.process.finalsts)
send_cmd_reply(fcmd_r, {'result': lst})
def dispatch_ast(astparam: AstParam):
iosb: iosbdef.IOSB_r_io_64 | None
iosb = astparam.iosb
buff = astparam.buff
match astparam.ptype:
case AstParamType.PROCTERM:
assert iosb is not None and buff is not None
res = buff.raw[: iosb.iosb_r_devdepend.iosb_r_bcnt_16.iosb_w_bcnt]
pid = iosb.iosb_r_devdepend.iosb_r_bcnt_16.iosb_l_dev_depend
try:
pgm = Program.running_processes[pid]
jfp
committed
acc = accdef.ACCDEF.parse(res)
print(acc)
logging.info(
f'Program {pgm.name} '
f'terminated {hex(pid)[2:].upper()}, '
jfp
committed
f'{acc.acc_l_finalsts}'
# if not stsdef.vms_status_success(acc.acc_l_finalsts):
pgm.create_process(True)
except KeyError:
pass
case AstParamType.CMD:
assert iosb is not None and buff is not None
res = buff.raw[: iosb.iosb_r_devdepend.iosb_r_bcnt_16.iosb_w_bcnt]
pid = iosb.iosb_r_devdepend.iosb_r_bcnt_16.iosb_l_dev_depend
case AstParamType.TIMER:
global current_tick
current_tick += 1
while True:
itm: PrioritizedItem
try:
itm = timer_queue.get_nowait()
except queue.Empty:
return
if itm.item.cancel:
continue
if itm.priority > current_tick:
timer_queue.put(itm)
return
itm.item.done = True
val: Program = itm.item.value
match itm.item.typ:
case TimerItemType.PROC_STARTING:
val.set_running()
case TimerItemType.PROC_BACKOFF:
val.create_process(False)
case TimerItemType.PROC_STOPPING:
val.kill()
def run(chan: int, chancmd: int):
astparam: AstParam
with (
mbxqio.MBXQIO(channel=chan) as fterm,
mbxqio.MBXQIO(channel=chancmd) as fcmd,
):
t_astctxt = qio_procterm(fterm) # noqa: F841
c_astctxt = qio_cmd(fcmd) # noqa: F841
for pgm in sorted(
Program.programs.values(), key=lambda pgm: pgm.priority
):
pgm.create_process(False)
timer_astctx.param = AstParam(AstParamType.TIMER)
starlet.setimr(daytim=timer_delay, reqidt=timer_astctx)
while True:
ret_ast_context: vmsast.AstContext
ret_ast_context = vmsast.get_completed(True) # type:ignore
astparam = ret_ast_context.param # type: ignore
iosb = astparam.iosb
if iosb and iosb.iosb_w_status == ssdef.SS__ENDOFFILE:
if astparam.ptype == AstParamType.PROCTERM:
t_astctxt = qio_procterm(fterm)
elif astparam.ptype == AstParamType.CMD:
c_astctxt = qio_cmd(fcmd)
continue
if iosb and not stsdef.vms_status_success(iosb.iosb_w_status):
raise IOError(iosb.iosb_w_status)
if astparam.ptype == AstParamType.PROCTERM:
t_astctxt = qio_procterm(fterm) # noqa: F841
elif astparam.ptype == AstParamType.CMD:
c_astctxt = qio_cmd(fcmd) # noqa: F841
elif astparam.ptype == AstParamType.TIMER:
timer_in_progress = False
if timer_queue.not_empty and not timer_in_progress:
timer_in_progress = True
start_timer()
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
def is_deamon() -> bool:
try:
pid = lib.get_logical(
b'SUPERVISORD' + b'_PID',
supervisord_table_name,
)[1]
cpid = lib.getjpi(jpidef.JPI__PID)[3]
return pid == cpid
except OSError as e:
if e.errno != ssdef.SS__NOLOGNAM:
raise
return False
def daemon_is_running() -> bool:
try:
v = lib.get_logical(
b'SUPERVISORD' + b'_PID',
supervisord_table_name,
)[1]
pid = int(v, 16)
lib.getjpi(jpidef.JPI__PID, pid)
return True
except OSError as e:
if e.errno == ssdef.SS__SUSPENDED:
return True
if e.errno == ssdef.SS__NONEXPR:
return False
raise
def daemonize(usrdaemon: str, comdaemon: str, stdout_file: str) -> int | None:
userpro = starlet.create_user_profile(usrnam=usrdaemon)[1]
persona_id = starlet.persona_create(usrpro=userpro)[1]
persona_previous_id = starlet.persona_assume(persona_id)[1]
error = None
try:
stsflg = (
prcdef.PRC_M_IMPERSONATE
| prcdef.PRC_M_PARSE_EXTENDED
| prcdef.PRC_M_KT_LIMIT
| prcdef.PRC_M_HIBER
)
pid = PidType(
starlet.creprc(
image=b'SYS$SYSTEM:LOGINOUT.EXE',
input=comdaemon.encode(),
output=stdout_file.encode(),
prcnam=b'SUPERVISORD',
kt_limit=1,
baspri=4,
stsflg=stsflg,
)[1]
)
lib.set_logical(
b'SUPERVISORD' + b'_PID',
hex(pid)[2:].upper(),
supervisord_table_name,
)
starlet.wake(pid)
return pid
except OSError as e:
error = e
return None
finally:
starlet.persona_assume(persona_previous_id)
starlet.persona_delete(persona_id)
if error is not None:
print(f"Can' create process SUPERVISORD, error {error}")
global logger, supervisorctl_pwd
# Required privileges: ALTPRI, SYSNAM, PRMMBX, IMPERSONATE, SETPRV, WORLD
parser = argparse.ArgumentParser(description='supervisord')
parser.add_argument(
'-c',
'--configuration',
type=argparse.FileType('r', encoding='latin1'),
default='./supervisord.conf',
help='Configuration file path (default ./supervisord.conf)',
)
jfp
committed
parser.add_argument(
'-p',
'--password',
required=False,
type=str,
default='',
help='Password for supervisord',
)
parser.add_argument(
'-n',
'--nodaemon',
action='store_true',
help='Run supervisord in the foreground',
jfp
committed
)
args = parser.parse_args()
jfp
committed
supervisorctl_pwd = args.password
config.read_file(args.configuration)
args.configuration.close()
nodaemon = (
config['supervisord'].getboolean('nodaemon', False) or args.nodaemon
)
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
if not nodaemon and not is_deamon():
usrdaemon = config['supervisord'].get('user', 'system')
stdout_file = config['supervisord'].get('stdout_file', 'NLA0:')
comdaemon = config['supervisord']['command']
pid = daemonize(usrdaemon, comdaemon, stdout_file)
if pid is None:
print('Unable to start supervisord daemon')
else:
print(f'Daemon started {hex(pid)[2:].upper()}')
exit(0)
logfile = config['supervisord'].get('logfile', 'supervisord.log')
logging_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
logging.basicConfig(
filename='/dev/null', level=logging.INFO, format=logging_format
)
formatter = logging.Formatter(logging_format)
main_log = logging.getLogger() # root handler
main_log.setLevel(logging.INFO)
logfn = crtl.from_vms(logfile)
if logfn is None:
print(f'{repr(logfile)} is invalid')
crtl.vms_exit(ssdef.SS__INVARG)
handler = logging.handlers.TimedRotatingFileHandler(
logfn, 'midnight', backupCount=10
)
handler.setFormatter(formatter)
main_log.addHandler(handler)
quotaslst = [
quota.strip().upper().split('=')
for quota in config[sn].get('quotas', '').split(',')
if quota != ''
]
prcquotas = []
for quotan, quotav in quotaslst:
if quotan not in quotas:
print(f'{sn}: {quotan}={quotav} not a valid quota')
crtl.vms_exit(ssdef.SS__INVARG)
if quotan == 'CPULM':
quotav = int(quotav) * 100
prcquotas.append((quotas[quotan], int(quotav)))
if prcquotas == []:
prcquotas = None
prvnames = [
name.strip().upper()
for name in config[sn].get('privileges', '').split(',')
if name != ''
]
prv: PrvMask | None
prv = None
for prvnam in prvnames: