ominfra 0.0.0.dev125__py3-none-any.whl → 0.0.0.dev127__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ominfra/clouds/aws/auth.py +1 -1
- ominfra/deploy/_executor.py +1 -1
- ominfra/deploy/poly/_main.py +1 -1
- ominfra/pyremote/_runcommands.py +1 -1
- ominfra/scripts/journald2aws.py +2 -2
- ominfra/scripts/supervisor.py +1825 -1217
- ominfra/supervisor/collections.py +52 -0
- ominfra/supervisor/context.py +2 -336
- ominfra/supervisor/datatypes.py +1 -63
- ominfra/supervisor/dispatchers.py +22 -338
- ominfra/supervisor/dispatchersimpl.py +342 -0
- ominfra/supervisor/groups.py +33 -110
- ominfra/supervisor/groupsimpl.py +86 -0
- ominfra/supervisor/inject.py +45 -13
- ominfra/supervisor/main.py +1 -1
- ominfra/supervisor/pipes.py +83 -0
- ominfra/supervisor/poller.py +6 -3
- ominfra/supervisor/privileges.py +65 -0
- ominfra/supervisor/processes.py +18 -0
- ominfra/supervisor/{process.py → processesimpl.py} +99 -317
- ominfra/supervisor/setup.py +38 -0
- ominfra/supervisor/setupimpl.py +261 -0
- ominfra/supervisor/signals.py +24 -16
- ominfra/supervisor/spawning.py +31 -0
- ominfra/supervisor/spawningimpl.py +347 -0
- ominfra/supervisor/supervisor.py +54 -78
- ominfra/supervisor/types.py +122 -39
- ominfra/supervisor/users.py +64 -0
- {ominfra-0.0.0.dev125.dist-info → ominfra-0.0.0.dev127.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev125.dist-info → ominfra-0.0.0.dev127.dist-info}/RECORD +34 -23
- {ominfra-0.0.0.dev125.dist-info → ominfra-0.0.0.dev127.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev125.dist-info → ominfra-0.0.0.dev127.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev125.dist-info → ominfra-0.0.0.dev127.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev125.dist-info → ominfra-0.0.0.dev127.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import abc
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
|
6
|
+
K = ta.TypeVar('K')
|
7
|
+
V = ta.TypeVar('V')
|
8
|
+
|
9
|
+
|
10
|
+
class KeyedCollectionAccessors(abc.ABC, ta.Generic[K, V]):
|
11
|
+
@property
|
12
|
+
@abc.abstractmethod
|
13
|
+
def _by_key(self) -> ta.Mapping[K, V]:
|
14
|
+
raise NotImplementedError
|
15
|
+
|
16
|
+
def __iter__(self) -> ta.Iterator[V]:
|
17
|
+
return iter(self._by_key.values())
|
18
|
+
|
19
|
+
def __len__(self) -> int:
|
20
|
+
return len(self._by_key)
|
21
|
+
|
22
|
+
def __contains__(self, key: K) -> bool:
|
23
|
+
return key in self._by_key
|
24
|
+
|
25
|
+
def __getitem__(self, key: K) -> V:
|
26
|
+
return self._by_key[key]
|
27
|
+
|
28
|
+
def get(self, key: K, default: ta.Optional[V] = None) -> ta.Optional[V]:
|
29
|
+
return self._by_key.get(key, default)
|
30
|
+
|
31
|
+
def items(self) -> ta.Iterator[ta.Tuple[K, V]]:
|
32
|
+
return iter(self._by_key.items())
|
33
|
+
|
34
|
+
|
35
|
+
class KeyedCollection(KeyedCollectionAccessors[K, V]):
|
36
|
+
def __init__(self, items: ta.Iterable[V]) -> None:
|
37
|
+
super().__init__()
|
38
|
+
|
39
|
+
by_key: ta.Dict[K, V] = {}
|
40
|
+
for v in items:
|
41
|
+
if (k := self._key(v)) in by_key:
|
42
|
+
raise KeyError(f'key {k} of {v} already registered by {by_key[k]}')
|
43
|
+
by_key[k] = v
|
44
|
+
self.__by_key = by_key
|
45
|
+
|
46
|
+
@property
|
47
|
+
def _by_key(self) -> ta.Mapping[K, V]:
|
48
|
+
return self.__by_key
|
49
|
+
|
50
|
+
@abc.abstractmethod
|
51
|
+
def _key(self, v: V) -> K:
|
52
|
+
raise NotImplementedError
|
ominfra/supervisor/context.py
CHANGED
@@ -1,34 +1,17 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
import errno
|
3
|
-
import fcntl
|
4
|
-
import grp
|
5
3
|
import os
|
6
|
-
import pwd
|
7
|
-
import re
|
8
|
-
import resource
|
9
|
-
import stat
|
10
4
|
import typing as ta
|
11
|
-
import warnings
|
12
5
|
|
13
6
|
from omlish.lite.logs import log
|
14
7
|
|
15
8
|
from .configs import ServerConfig
|
16
|
-
from .datatypes import gid_for_uid
|
17
|
-
from .datatypes import name_to_uid
|
18
|
-
from .exceptions import NoPermissionError
|
19
|
-
from .exceptions import NotExecutableError
|
20
|
-
from .exceptions import NotFoundError
|
21
9
|
from .poller import Poller
|
22
10
|
from .states import SupervisorState
|
23
11
|
from .types import Process
|
24
12
|
from .types import ServerContext
|
25
|
-
from .
|
13
|
+
from .types import ServerEpoch
|
26
14
|
from .utils import mktempfile
|
27
|
-
from .utils import real_exit
|
28
|
-
from .utils import try_unlink
|
29
|
-
|
30
|
-
|
31
|
-
ServerEpoch = ta.NewType('ServerEpoch', int)
|
32
15
|
|
33
16
|
|
34
17
|
class ServerContextImpl(ServerContext):
|
@@ -48,16 +31,6 @@ class ServerContextImpl(ServerContext):
|
|
48
31
|
self._pid_history: ta.Dict[int, Process] = {}
|
49
32
|
self._state: SupervisorState = SupervisorState.RUNNING
|
50
33
|
|
51
|
-
if config.user is not None:
|
52
|
-
uid = name_to_uid(config.user)
|
53
|
-
self._uid: ta.Optional[int] = uid
|
54
|
-
self._gid: ta.Optional[int] = gid_for_uid(uid)
|
55
|
-
else:
|
56
|
-
self._uid = None
|
57
|
-
self._gid = None
|
58
|
-
|
59
|
-
self._unlink_pidfile = False
|
60
|
-
|
61
34
|
@property
|
62
35
|
def config(self) -> ServerConfig:
|
63
36
|
return self._config
|
@@ -81,15 +54,7 @@ class ServerContextImpl(ServerContext):
|
|
81
54
|
def pid_history(self) -> ta.Dict[int, Process]:
|
82
55
|
return self._pid_history
|
83
56
|
|
84
|
-
|
85
|
-
def uid(self) -> ta.Optional[int]:
|
86
|
-
return self._uid
|
87
|
-
|
88
|
-
@property
|
89
|
-
def gid(self) -> ta.Optional[int]:
|
90
|
-
return self._gid
|
91
|
-
|
92
|
-
##
|
57
|
+
#
|
93
58
|
|
94
59
|
def waitpid(self) -> ta.Tuple[ta.Optional[int], ta.Optional[int]]:
|
95
60
|
# Need pthread_sigmask here to avoid concurrent sigchld, but Python doesn't offer in Python < 3.4. There is
|
@@ -109,166 +74,6 @@ class ServerContextImpl(ServerContext):
|
|
109
74
|
pid, sts = None, None
|
110
75
|
return pid, sts
|
111
76
|
|
112
|
-
def set_uid_or_exit(self) -> None:
|
113
|
-
"""
|
114
|
-
Set the uid of the supervisord process. Called during supervisord startup only. No return value. Exits the
|
115
|
-
process via usage() if privileges could not be dropped.
|
116
|
-
"""
|
117
|
-
|
118
|
-
if self.uid is None:
|
119
|
-
if os.getuid() == 0:
|
120
|
-
warnings.warn(
|
121
|
-
'Supervisor is running as root. Privileges were not dropped because no user is specified in the '
|
122
|
-
'config file. If you intend to run as root, you can set user=root in the config file to avoid '
|
123
|
-
'this message.',
|
124
|
-
)
|
125
|
-
else:
|
126
|
-
msg = drop_privileges(self.uid)
|
127
|
-
if msg is None:
|
128
|
-
log.info('Set uid to user %s succeeded', self.uid)
|
129
|
-
else: # failed to drop privileges
|
130
|
-
raise RuntimeError(msg)
|
131
|
-
|
132
|
-
def set_rlimits_or_exit(self) -> None:
|
133
|
-
"""
|
134
|
-
Set the rlimits of the supervisord process. Called during supervisord startup only. No return value. Exits
|
135
|
-
the process via usage() if any rlimits could not be set.
|
136
|
-
"""
|
137
|
-
|
138
|
-
limits = []
|
139
|
-
|
140
|
-
if hasattr(resource, 'RLIMIT_NOFILE'):
|
141
|
-
limits.append({
|
142
|
-
'msg': (
|
143
|
-
'The minimum number of file descriptors required to run this process is %(min_limit)s as per the '
|
144
|
-
'"minfds" command-line argument or config file setting. The current environment will only allow '
|
145
|
-
'you to open %(hard)s file descriptors. Either raise the number of usable file descriptors in '
|
146
|
-
'your environment (see README.rst) or lower the minfds setting in the config file to allow the '
|
147
|
-
'process to start.'
|
148
|
-
),
|
149
|
-
'min': self.config.minfds,
|
150
|
-
'resource': resource.RLIMIT_NOFILE,
|
151
|
-
'name': 'RLIMIT_NOFILE',
|
152
|
-
})
|
153
|
-
|
154
|
-
if hasattr(resource, 'RLIMIT_NPROC'):
|
155
|
-
limits.append({
|
156
|
-
'msg': (
|
157
|
-
'The minimum number of available processes required to run this program is %(min_limit)s as per '
|
158
|
-
'the "minprocs" command-line argument or config file setting. The current environment will only '
|
159
|
-
'allow you to open %(hard)s processes. Either raise the number of usable processes in your '
|
160
|
-
'environment (see README.rst) or lower the minprocs setting in the config file to allow the '
|
161
|
-
'program to start.'
|
162
|
-
),
|
163
|
-
'min': self.config.minprocs,
|
164
|
-
'resource': resource.RLIMIT_NPROC,
|
165
|
-
'name': 'RLIMIT_NPROC',
|
166
|
-
})
|
167
|
-
|
168
|
-
for limit in limits:
|
169
|
-
min_limit = limit['min']
|
170
|
-
res = limit['resource']
|
171
|
-
msg = limit['msg']
|
172
|
-
name = limit['name']
|
173
|
-
|
174
|
-
soft, hard = resource.getrlimit(res) # type: ignore
|
175
|
-
|
176
|
-
# -1 means unlimited
|
177
|
-
if soft < min_limit and soft != -1: # type: ignore
|
178
|
-
if hard < min_limit and hard != -1: # type: ignore
|
179
|
-
# setrlimit should increase the hard limit if we are root, if not then setrlimit raises and we print
|
180
|
-
# usage
|
181
|
-
hard = min_limit # type: ignore
|
182
|
-
|
183
|
-
try:
|
184
|
-
resource.setrlimit(res, (min_limit, hard)) # type: ignore
|
185
|
-
log.info('Increased %s limit to %s', name, min_limit)
|
186
|
-
except (resource.error, ValueError):
|
187
|
-
raise RuntimeError(msg % dict( # type: ignore # noqa
|
188
|
-
min_limit=min_limit,
|
189
|
-
res=res,
|
190
|
-
name=name,
|
191
|
-
soft=soft,
|
192
|
-
hard=hard,
|
193
|
-
))
|
194
|
-
|
195
|
-
def cleanup(self) -> None:
|
196
|
-
if self._unlink_pidfile:
|
197
|
-
try_unlink(self.config.pidfile)
|
198
|
-
self._poller.close()
|
199
|
-
|
200
|
-
def cleanup_fds(self) -> None:
|
201
|
-
# try to close any leaked file descriptors (for reload)
|
202
|
-
start = 5
|
203
|
-
os.closerange(start, self.config.minfds)
|
204
|
-
|
205
|
-
def clear_auto_child_logdir(self) -> None:
|
206
|
-
# must be called after realize()
|
207
|
-
child_logdir = self.config.child_logdir
|
208
|
-
fnre = re.compile(rf'.+?---{self.config.identifier}-\S+\.log\.?\d{{0,4}}')
|
209
|
-
try:
|
210
|
-
filenames = os.listdir(child_logdir)
|
211
|
-
except OSError:
|
212
|
-
log.warning('Could not clear child_log dir')
|
213
|
-
return
|
214
|
-
|
215
|
-
for filename in filenames:
|
216
|
-
if fnre.match(filename):
|
217
|
-
pathname = os.path.join(child_logdir, filename)
|
218
|
-
try:
|
219
|
-
os.remove(pathname)
|
220
|
-
except OSError:
|
221
|
-
log.warning('Failed to clean up %r', pathname)
|
222
|
-
|
223
|
-
def daemonize(self) -> None:
|
224
|
-
self._poller.before_daemonize()
|
225
|
-
self._daemonize()
|
226
|
-
self._poller.after_daemonize()
|
227
|
-
|
228
|
-
def _daemonize(self) -> None:
|
229
|
-
# To daemonize, we need to become the leader of our own session (process) group. If we do not, signals sent to
|
230
|
-
# our parent process will also be sent to us. This might be bad because signals such as SIGINT can be sent to
|
231
|
-
# our parent process during normal (uninteresting) operations such as when we press Ctrl-C in the parent
|
232
|
-
# terminal window to escape from a logtail command. To disassociate ourselves from our parent's session group we
|
233
|
-
# use os.setsid. It means "set session id", which has the effect of disassociating a process from is current
|
234
|
-
# session and process group and setting itself up as a new session leader.
|
235
|
-
#
|
236
|
-
# Unfortunately we cannot call setsid if we're already a session group leader, so we use "fork" to make a copy
|
237
|
-
# of ourselves that is guaranteed to not be a session group leader.
|
238
|
-
#
|
239
|
-
# We also change directories, set stderr and stdout to null, and change our umask.
|
240
|
-
#
|
241
|
-
# This explanation was (gratefully) garnered from
|
242
|
-
# http://www.cems.uwe.ac.uk/~irjohnso/coursenotes/lrc/system/daemons/d3.htm
|
243
|
-
|
244
|
-
pid = os.fork()
|
245
|
-
if pid != 0:
|
246
|
-
# Parent
|
247
|
-
log.debug('supervisord forked; parent exiting')
|
248
|
-
real_exit(0)
|
249
|
-
|
250
|
-
# Child
|
251
|
-
log.info('daemonizing the supervisord process')
|
252
|
-
if self.config.directory:
|
253
|
-
try:
|
254
|
-
os.chdir(self.config.directory)
|
255
|
-
except OSError as err:
|
256
|
-
log.critical("can't chdir into %r: %s", self.config.directory, err)
|
257
|
-
else:
|
258
|
-
log.info('set current directory: %r', self.config.directory)
|
259
|
-
|
260
|
-
os.dup2(0, os.open('/dev/null', os.O_RDONLY))
|
261
|
-
os.dup2(1, os.open('/dev/null', os.O_WRONLY))
|
262
|
-
os.dup2(2, os.open('/dev/null', os.O_WRONLY))
|
263
|
-
|
264
|
-
os.setsid()
|
265
|
-
|
266
|
-
os.umask(self.config.umask)
|
267
|
-
|
268
|
-
# XXX Stevens, in his Advanced Unix book, section 13.3 (page 417) recommends calling umask(0) and closing unused
|
269
|
-
# file descriptors. In his Network Programming book, he additionally recommends ignoring SIGHUP and forking
|
270
|
-
# again after the setsid() call, for obscure SVR4 reasons.
|
271
|
-
|
272
77
|
def get_auto_child_log_name(self, name: str, identifier: str, channel: str) -> str:
|
273
78
|
prefix = f'{name}-{channel}---{identifier}-'
|
274
79
|
logfile = mktempfile(
|
@@ -277,142 +82,3 @@ class ServerContextImpl(ServerContext):
|
|
277
82
|
dir=self.config.child_logdir,
|
278
83
|
)
|
279
84
|
return logfile
|
280
|
-
|
281
|
-
def write_pidfile(self) -> None:
|
282
|
-
pid = os.getpid()
|
283
|
-
try:
|
284
|
-
with open(self.config.pidfile, 'w') as f:
|
285
|
-
f.write(f'{pid}\n')
|
286
|
-
except OSError:
|
287
|
-
log.critical('could not write pidfile %s', self.config.pidfile)
|
288
|
-
else:
|
289
|
-
self._unlink_pidfile = True
|
290
|
-
log.info('supervisord started with pid %s', pid)
|
291
|
-
|
292
|
-
|
293
|
-
def drop_privileges(user: ta.Union[int, str, None]) -> ta.Optional[str]:
|
294
|
-
"""
|
295
|
-
Drop privileges to become the specified user, which may be a username or uid. Called for supervisord startup
|
296
|
-
and when spawning subprocesses. Returns None on success or a string error message if privileges could not be
|
297
|
-
dropped.
|
298
|
-
"""
|
299
|
-
|
300
|
-
if user is None:
|
301
|
-
return 'No user specified to setuid to!'
|
302
|
-
|
303
|
-
# get uid for user, which can be a number or username
|
304
|
-
try:
|
305
|
-
uid = int(user)
|
306
|
-
except ValueError:
|
307
|
-
try:
|
308
|
-
pwrec = pwd.getpwnam(user) # type: ignore
|
309
|
-
except KeyError:
|
310
|
-
return f"Can't find username {user!r}"
|
311
|
-
uid = pwrec[2]
|
312
|
-
else:
|
313
|
-
try:
|
314
|
-
pwrec = pwd.getpwuid(uid)
|
315
|
-
except KeyError:
|
316
|
-
return f"Can't find uid {uid!r}"
|
317
|
-
|
318
|
-
current_uid = os.getuid()
|
319
|
-
|
320
|
-
if current_uid == uid:
|
321
|
-
# do nothing and return successfully if the uid is already the current one. this allows a supervisord
|
322
|
-
# running as an unprivileged user "foo" to start a process where the config has "user=foo" (same user) in
|
323
|
-
# it.
|
324
|
-
return None
|
325
|
-
|
326
|
-
if current_uid != 0:
|
327
|
-
return "Can't drop privilege as nonroot user"
|
328
|
-
|
329
|
-
gid = pwrec[3]
|
330
|
-
if hasattr(os, 'setgroups'):
|
331
|
-
user = pwrec[0]
|
332
|
-
groups = [grprec[2] for grprec in grp.getgrall() if user in grprec[3]]
|
333
|
-
|
334
|
-
# always put our primary gid first in this list, otherwise we can lose group info since sometimes the first
|
335
|
-
# group in the setgroups list gets overwritten on the subsequent setgid call (at least on freebsd 9 with
|
336
|
-
# python 2.7 - this will be safe though for all unix /python version combos)
|
337
|
-
groups.insert(0, gid)
|
338
|
-
try:
|
339
|
-
os.setgroups(groups)
|
340
|
-
except OSError:
|
341
|
-
return 'Could not set groups of effective user'
|
342
|
-
|
343
|
-
try:
|
344
|
-
os.setgid(gid)
|
345
|
-
except OSError:
|
346
|
-
return 'Could not set group id of effective user'
|
347
|
-
|
348
|
-
os.setuid(uid)
|
349
|
-
|
350
|
-
return None
|
351
|
-
|
352
|
-
|
353
|
-
def make_pipes(stderr=True) -> ta.Mapping[str, int]:
|
354
|
-
"""
|
355
|
-
Create pipes for parent to child stdin/stdout/stderr communications. Open fd in non-blocking mode so we can
|
356
|
-
read them in the mainloop without blocking. If stderr is False, don't create a pipe for stderr.
|
357
|
-
"""
|
358
|
-
|
359
|
-
pipes: ta.Dict[str, ta.Optional[int]] = {
|
360
|
-
'child_stdin': None,
|
361
|
-
'stdin': None,
|
362
|
-
'stdout': None,
|
363
|
-
'child_stdout': None,
|
364
|
-
'stderr': None,
|
365
|
-
'child_stderr': None,
|
366
|
-
}
|
367
|
-
|
368
|
-
try:
|
369
|
-
stdin, child_stdin = os.pipe()
|
370
|
-
pipes['child_stdin'], pipes['stdin'] = stdin, child_stdin
|
371
|
-
|
372
|
-
stdout, child_stdout = os.pipe()
|
373
|
-
pipes['stdout'], pipes['child_stdout'] = stdout, child_stdout
|
374
|
-
|
375
|
-
if stderr:
|
376
|
-
stderr, child_stderr = os.pipe()
|
377
|
-
pipes['stderr'], pipes['child_stderr'] = stderr, child_stderr
|
378
|
-
|
379
|
-
for fd in (pipes['stdout'], pipes['stderr'], pipes['stdin']):
|
380
|
-
if fd is not None:
|
381
|
-
flags = fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NDELAY
|
382
|
-
fcntl.fcntl(fd, fcntl.F_SETFL, flags)
|
383
|
-
|
384
|
-
return pipes # type: ignore
|
385
|
-
|
386
|
-
except OSError:
|
387
|
-
for fd in pipes.values():
|
388
|
-
if fd is not None:
|
389
|
-
close_fd(fd)
|
390
|
-
raise
|
391
|
-
|
392
|
-
|
393
|
-
def close_parent_pipes(pipes: ta.Mapping[str, int]) -> None:
|
394
|
-
for fdname in ('stdin', 'stdout', 'stderr'):
|
395
|
-
fd = pipes.get(fdname)
|
396
|
-
if fd is not None:
|
397
|
-
close_fd(fd)
|
398
|
-
|
399
|
-
|
400
|
-
def close_child_pipes(pipes: ta.Mapping[str, int]) -> None:
|
401
|
-
for fdname in ('child_stdin', 'child_stdout', 'child_stderr'):
|
402
|
-
fd = pipes.get(fdname)
|
403
|
-
if fd is not None:
|
404
|
-
close_fd(fd)
|
405
|
-
|
406
|
-
|
407
|
-
def check_execv_args(filename, argv, st) -> None:
|
408
|
-
if st is None:
|
409
|
-
raise NotFoundError(f"can't find command {filename!r}")
|
410
|
-
|
411
|
-
elif stat.S_ISDIR(st[stat.ST_MODE]):
|
412
|
-
raise NotExecutableError(f'command at {filename!r} is a directory')
|
413
|
-
|
414
|
-
elif not (stat.S_IMODE(st[stat.ST_MODE]) & 0o111):
|
415
|
-
raise NotExecutableError(f'command at {filename!r} is not executable')
|
416
|
-
|
417
|
-
elif not os.access(filename, os.X_OK):
|
418
|
-
raise NoPermissionError(f'no permission to run command {filename!r}')
|
ominfra/supervisor/datatypes.py
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
# ruff: noqa: UP007
|
2
|
-
import grp
|
3
2
|
import logging
|
4
3
|
import os
|
5
|
-
import pwd
|
6
|
-
import signal
|
7
4
|
import typing as ta
|
8
5
|
|
9
6
|
|
@@ -36,43 +33,7 @@ def logfile_name(val):
|
|
36
33
|
return existing_dirpath(val)
|
37
34
|
|
38
35
|
|
39
|
-
|
40
|
-
try:
|
41
|
-
uid = int(name)
|
42
|
-
except ValueError:
|
43
|
-
try:
|
44
|
-
pwdrec = pwd.getpwnam(name)
|
45
|
-
except KeyError:
|
46
|
-
raise ValueError(f'Invalid user name {name}') # noqa
|
47
|
-
uid = pwdrec[2]
|
48
|
-
else:
|
49
|
-
try:
|
50
|
-
pwd.getpwuid(uid) # check if uid is valid
|
51
|
-
except KeyError:
|
52
|
-
raise ValueError(f'Invalid user id {name}') # noqa
|
53
|
-
return uid
|
54
|
-
|
55
|
-
|
56
|
-
def name_to_gid(name: str) -> int:
|
57
|
-
try:
|
58
|
-
gid = int(name)
|
59
|
-
except ValueError:
|
60
|
-
try:
|
61
|
-
grprec = grp.getgrnam(name)
|
62
|
-
except KeyError:
|
63
|
-
raise ValueError(f'Invalid group name {name}') # noqa
|
64
|
-
gid = grprec[2]
|
65
|
-
else:
|
66
|
-
try:
|
67
|
-
grp.getgrgid(gid) # check if gid is valid
|
68
|
-
except KeyError:
|
69
|
-
raise ValueError(f'Invalid group id {name}') # noqa
|
70
|
-
return gid
|
71
|
-
|
72
|
-
|
73
|
-
def gid_for_uid(uid: int) -> int:
|
74
|
-
pwrec = pwd.getpwuid(uid)
|
75
|
-
return pwrec[3]
|
36
|
+
##
|
76
37
|
|
77
38
|
|
78
39
|
def octal_type(arg: ta.Union[str, int]) -> int:
|
@@ -144,29 +105,6 @@ byte_size = SuffixMultiplier({
|
|
144
105
|
})
|
145
106
|
|
146
107
|
|
147
|
-
# all valid signal numbers
|
148
|
-
SIGNUMS = [getattr(signal, k) for k in dir(signal) if k.startswith('SIG')]
|
149
|
-
|
150
|
-
|
151
|
-
def signal_number(value: ta.Union[int, str]) -> int:
|
152
|
-
try:
|
153
|
-
num = int(value)
|
154
|
-
|
155
|
-
except (ValueError, TypeError):
|
156
|
-
name = value.strip().upper() # type: ignore
|
157
|
-
if not name.startswith('SIG'):
|
158
|
-
name = f'SIG{name}'
|
159
|
-
|
160
|
-
num = getattr(signal, name, None) # type: ignore
|
161
|
-
if num is None:
|
162
|
-
raise ValueError(f'value {value!r} is not a valid signal name') # noqa
|
163
|
-
|
164
|
-
if num not in SIGNUMS:
|
165
|
-
raise ValueError(f'value {value!r} is not a valid signal number')
|
166
|
-
|
167
|
-
return num
|
168
|
-
|
169
|
-
|
170
108
|
class RestartWhenExitUnexpected:
|
171
109
|
pass
|
172
110
|
|