Browse Source

update: process utils

dev
dnomd343 2 years ago
parent
commit
f7399482ce
  1. 90
      Utils/Process.py

90
Basis/Process.py → Utils/Process.py

@ -6,12 +6,12 @@ import copy
import time import time
import ctypes import ctypes
import signal import signal
from Basis.Logger import logging from Utils.Logger import logger
from Basis.Functions import genFlag from Utils.Common import genFlag
from Basis.Exception import processException from Utils.Exception import processException
from subprocess import Popen, STDOUT, DEVNULL from subprocess import Popen, STDOUT, DEVNULL
libcPaths = [ libcSysPaths = [
'/usr/lib/libc.so.6', # CentOS '/usr/lib/libc.so.6', # CentOS
'/usr/lib64/libc.so.6', '/usr/lib64/libc.so.6',
'/lib/libc.musl-i386.so.1', # Alpine '/lib/libc.musl-i386.so.1', # Alpine
@ -23,18 +23,18 @@ libcPaths = [
] ]
libcPath = None libcPath = None
for libc in libcPaths: for path in libcSysPaths:
if os.path.exists(libc): # try to locate libc.so if os.path.exists(path): # try to locate libc.so
libcPath = libc libcPath = path
break break
if libcPath is None: # lost libc.so -> unable to utilize prctl if libcPath is None: # lost libc.so -> unable to utilize prctl
logging.warning('libc.so not found') logger.warning('libc.so not found')
else: else:
logging.info('libc.so -> ' + str(libcPath)) logger.info('libc.so -> %s' % libcPath)
class Process(object): class Process(object):
""" Manage a sub process and it's file. """ Manage a sub process and it's configure file.
Arguments: Arguments:
cmd: Command list, which use to start the program. cmd: Command list, which use to start the program.
@ -65,34 +65,34 @@ class Process(object):
def __checkWorkDir(self) -> None: # check if the working directory is normal def __checkWorkDir(self) -> None: # check if the working directory is normal
if os.path.isdir(self.workDir): if os.path.isdir(self.workDir):
return return
logging.warning('[%s] Work directory %s not exist' % (self.id, self.workDir)) logger.warning('[%s] Work directory %s not exist' % (self.id, self.workDir))
try: try:
os.makedirs(self.workDir) # just like `mkdir -p ...` os.makedirs(self.workDir) # just like `mkdir -p ...`
logging.info('[%s] New directory -> %s' % (self.id, self.workDir)) logger.info('[%s] New directory -> %s' % (self.id, self.workDir))
except: except:
if os.path.exists(self.workDir): if os.path.exists(self.workDir):
logging.error('[%s] %s already exist but not folder' % (self.id, self.workDir)) logger.error('[%s] %s already exist but not folder' % (self.id, self.workDir))
else: else:
logging.error('[%s] Unable to create new folder -> %s' % (self.id, self.workDir)) logger.error('[%s] Unable to create new folder -> %s' % (self.id, self.workDir))
raise processException('Working directory error') # fatal error raise processException('Working directory error') # fatal error
def __killProcess(self, killSignal: int) -> None: def __killProcess(self, killSignal: int) -> None:
try: try:
pgid = os.getpgid(self.__process.pid) # progress group id pgid = os.getpgid(self.__process.pid) # progress group id
os.killpg(pgid, killSignal) # kill sub process group os.killpg(pgid, killSignal) # kill sub process group
logging.debug('[%s] Send signal %i to PGID %i' % (self.id, killSignal, pgid)) logger.debug('[%s] Send signal %i to PGID %i' % (self.id, killSignal, pgid))
except: except:
logging.warning('[%s] Failed to get PGID of sub process (PID = %i)' % (self.id, self.__process.pid)) logger.warning('[%s] Failed to get PGID of sub process (PID = %i)' % (self.id, self.__process.pid))
def __deleteFile(self, filePath: str) -> None: def __deleteFile(self, filePath: str) -> None:
if not os.path.isfile(filePath): # file not found (or not a file) if not os.path.isfile(filePath): # file not found (or not a file)
logging.warning('[%s] File %s not found' % (self.id, filePath)) logger.warning('[%s] File %s not found' % (self.id, filePath))
return return
try: try:
os.remove(filePath) # remove config file os.remove(filePath) # remove config file
logging.debug('[%s] File %s deleted successfully' % (self.id, filePath)) logger.debug('[%s] File %s deleted successfully' % (self.id, filePath))
except: except:
logging.error('[%s] Unable to delete file %s' % (self.id, filePath)) logger.error('[%s] Unable to delete file %s' % (self.id, filePath))
def __init__(self, workDir: str, taskId: str = '', isStart: bool = True, def __init__(self, workDir: str, taskId: str = '', isStart: bool = True,
cmd: str or list or None = None, env: dict or None = None, file: dict or list or None = None) -> None: cmd: str or list or None = None, env: dict or None = None, file: dict or list or None = None) -> None:
@ -102,53 +102,53 @@ class Process(object):
self.cmd = copy.copy([cmd] if type(cmd) == str else cmd) # depth = 1 self.cmd = copy.copy([cmd] if type(cmd) == str else cmd) # depth = 1
self.file = copy.deepcopy([file] if type(file) == dict else file) # depth = 2 self.file = copy.deepcopy([file] if type(file) == dict else file) # depth = 2
self.__checkWorkDir() # ensure the working direction is normal self.__checkWorkDir() # ensure the working direction is normal
logging.debug('[%s] Process command -> %s (%s)' % (self.id, self.cmd, self.env)) logger.debug('[%s] Process command -> %s (%s)' % (self.id, self.cmd, self.env))
if self.file is not None: if self.file is not None:
if len(self.file) > 1: if len(self.file) > 1:
logging.debug('[%s] Manage %i files' % (self.id, len(self.file))) logger.debug('[%s] Manage %i files' % (self.id, len(self.file)))
for file in self.file: # traverse all files for file in self.file: # traverse all files
if not isStart: # don't print log twice if not isStart: # don't print log twice
logging.debug('[%s] File %s -> %s' % (self.id, file['path'], file['content'])) logger.debug('[%s] File %s -> %s' % (self.id, file['path'], file['content']))
if isStart: if isStart:
self.start() self.start()
def setCmd(self, cmd: str or list) -> None: def setCmd(self, cmd: str or list) -> None:
self.cmd = copy.copy([cmd] if type(cmd) == str else cmd) self.cmd = copy.copy([cmd] if type(cmd) == str else cmd)
logging.info('[%s] Process setting command -> %s' % (self.id, self.cmd)) logger.info('[%s] Process setting command -> %s' % (self.id, self.cmd))
def setEnv(self, env: dict or None) -> None: def setEnv(self, env: dict or None) -> None:
self.env = copy.copy(env) self.env = copy.copy(env)
logging.info('[%s] Process setting environ -> %s' % (self.id, self.env)) logger.info('[%s] Process setting environ -> %s' % (self.id, self.env))
def setFile(self, file: dict or list or None) -> None: def setFile(self, file: dict or list or None) -> None:
self.file = copy.deepcopy([file] if type(file) == dict else file) self.file = copy.deepcopy([file] if type(file) == dict else file)
if self.file is None: if self.file is None:
logging.info('[%s] Process setting file -> None' % self.id) logger.info('[%s] Process setting file -> None' % self.id)
return return
for file in self.file: # traverse all files for file in self.file: # traverse all files
logging.info('[%s] Process setting file %s -> %s' % (self.id, file['path'], file['content'])) logger.info('[%s] Process setting file %s -> %s' % (self.id, file['path'], file['content']))
def start(self, isCapture: bool = True) -> None: def start(self, isCapture: bool = True) -> None:
self.__capture = isCapture self.__capture = isCapture
logging.debug('[%s] Process ready to start (%s)' % ( logger.debug('[%s] Process ready to start (%s output capture)' % (
self.id, ('with' if self.__capture else 'without') + ' output capture' self.id, 'with' if self.__capture else 'without'
)) ))
if self.cmd is None: # ERROR CASE if self.cmd is None: # ERROR CASE
logging.error('[%s] Process miss start command' % self.id) logger.error('[%s] Process miss start command' % self.id)
raise processException('Miss start command') raise processException('Miss start command')
if self.__process is not None and self.__process.poll() is None: # ERROR CASE if self.__process is not None and self.__process.poll() is None: # ERROR CASE
logging.error('[%s] Process try to start but it is running' % self.id) logger.error('[%s] Process try to start but it is running' % self.id)
raise processException('Process is still running') raise processException('Process is still running')
if self.env is not None and 'PATH' not in self.env and '/' not in self.cmd[0]: # WARNING CASE if self.env is not None and 'PATH' not in self.env and '/' not in self.cmd[0]: # WARNING CASE
logging.warning('[%s] Executable file in relative path but miss PATH in environ' % self.id) logger.warning('[%s] Executable file in relative path but miss PATH in environ' % self.id)
if self.file is not None: # create and write file contents if self.file is not None: # create and write file contents
for file in self.file: for file in self.file:
with open(file['path'], 'w', encoding = 'utf-8') as fileObject: # save file content with open(file['path'], 'w', encoding = 'utf-8') as fileObject: # save file content
fileObject.write(file['content']) fileObject.write(file['content'])
logging.debug('[%s] File %s -> %s' % (self.id, file['path'], file['content'])) logger.debug('[%s] File %s -> %s' % (self.id, file['path'], file['content']))
if self.__capture: # with output capture if self.__capture: # with output capture
self.__logfile = os.path.join(self.workDir, self.id + '.log') self.__logfile = os.path.join(self.workDir, '%s.log' % self.id)
logging.debug('[%s] Process output capture -> %s' % (self.id, self.__logfile)) logger.debug('[%s] Process output capture -> %s' % (self.id, self.__logfile))
stdout = open(self.__logfile, 'w', encoding = 'utf-8') stdout = open(self.__logfile, 'w', encoding = 'utf-8')
stderr = STDOUT # combine the stderr with stdout stderr = STDOUT # combine the stderr with stdout
else: # discard all the output of sub process else: # discard all the output of sub process
@ -161,48 +161,48 @@ class Process(object):
preexec_fn = None if libcPath is None else Process.__preExec preexec_fn = None if libcPath is None else Process.__preExec
) )
except Exception as exp: except Exception as exp:
logging.error('[%s] Process unable to start -> %s' % (self.id, exp)) logger.error('[%s] Process unable to start -> %s' % (self.id, exp))
raise processException('Unable to start process') raise processException('Unable to start process')
logging.info('[%s] Process running -> PID = %i' % (self.id, self.__process.pid)) logger.info('[%s] Process running -> PID = %i' % (self.id, self.__process.pid))
def signal(self, signalNum: int) -> None: # send specified signal to sub process def signal(self, signalNum: int) -> None: # send specified signal to sub process
try: try:
signalName = signal.Signals(signalNum).name signalName = signal.Signals(signalNum).name
except: except:
signalName = 'unknown' signalName = 'unknown'
logging.info('[%s] Send signal -> %i (%s)' % (self.id, signalNum, signalName)) logger.info('[%s] Send signal -> %i (%s)' % (self.id, signalNum, signalName))
self.__process.send_signal(signalNum) self.__process.send_signal(signalNum)
def status(self) -> bool: # check if the sub process is still running def status(self) -> bool: # check if the sub process is still running
status = self.__process.poll() is None status = self.__process.poll() is None
logging.debug('[%s] Process check status -> %s' % (self.id, 'running' if status else 'exit')) logger.debug('[%s] Process check status -> %s' % (self.id, 'running' if status else 'exit'))
return status return status
def wait(self, timeout: int or None = None) -> None: # blocking wait sub process def wait(self, timeout: int or None = None) -> None: # blocking wait sub process
logging.info('[%s] Process wait -> timeout = %s' % (self.id, str(timeout))) logger.info('[%s] Process wait -> timeout = %s' % (self.id, str(timeout)))
try: try:
self.__process.wait(timeout = timeout) self.__process.wait(timeout = timeout)
logging.info('[%s] Process wait timeout -> exit' % self.id) logger.info('[%s] Process wait timeout -> exit' % self.id)
except: except:
logging.info('[%s] Process wait timeout -> running' % self.id) logger.info('[%s] Process wait timeout -> running' % self.id)
def quit(self, isForce: bool = False, waitTime: int = 50) -> None: # wait 50ms in default def quit(self, isForce: bool = False, waitTime: int = 50) -> None: # wait 50ms in default
killSignal = signal.SIGKILL if isForce else signal.SIGTERM # 9 -> force kill / 15 -> terminate signal killSignal = signal.SIGKILL if isForce else signal.SIGTERM # 9 -> force kill / 15 -> terminate signal
logging.debug('[%s] Kill signal -> %i (%s)' % (self.id, killSignal, signal.Signals(killSignal).name)) logger.debug('[%s] Kill signal -> %i (%s)' % (self.id, killSignal, signal.Signals(killSignal).name))
self.__killProcess(killSignal) self.__killProcess(killSignal)
time.sleep(waitTime / 1000) # sleep (ms -> s) time.sleep(waitTime / 1000) # sleep (ms -> s)
while self.__process.poll() is None: # confirm sub process exit while self.__process.poll() is None: # confirm sub process exit
self.__killProcess(killSignal) self.__killProcess(killSignal)
time.sleep(waitTime / 1000) time.sleep(waitTime / 1000)
logging.info('[%s] Process terminated -> PID = %i' % (self.id, self.__process.pid)) logger.info('[%s] Process terminated -> PID = %i' % (self.id, self.__process.pid))
if self.__capture: if self.__capture:
try: try:
with open(self.__logfile, 'r', encoding = 'utf-8') as fileObject: # read sub process output with open(self.__logfile, 'r', encoding = 'utf-8') as fileObject: # read sub process output
self.output = fileObject.read() self.output = fileObject.read()
logging.debug('[%s] Process output capture -> length = %s' % (self.id, len(self.output))) logger.debug('[%s] Process output capture -> length = %s' % (self.id, len(self.output)))
self.__deleteFile(self.__logfile) self.__deleteFile(self.__logfile)
except: except:
logging.error('[%s] Failed to read capture file -> %s' % (self.id, self.__logfile)) logger.error('[%s] Failed to read capture file -> %s' % (self.id, self.__logfile))
if self.file is not None: # with config file if self.file is not None: # with config file
for file in self.file: for file in self.file:
self.__deleteFile(file['path']) self.__deleteFile(file['path'])
Loading…
Cancel
Save