|
|
@ -6,12 +6,12 @@ import copy |
|
|
|
import time |
|
|
|
import ctypes |
|
|
|
import signal |
|
|
|
from Basis.Logger import logging |
|
|
|
from Basis.Functions import genFlag |
|
|
|
from Basis.Exception import processException |
|
|
|
from Utils.Logger import logger |
|
|
|
from Utils.Common import genFlag |
|
|
|
from Utils.Exception import processException |
|
|
|
from subprocess import Popen, STDOUT, DEVNULL |
|
|
|
|
|
|
|
libcPaths = [ |
|
|
|
libcSysPaths = [ |
|
|
|
'/usr/lib/libc.so.6', # CentOS |
|
|
|
'/usr/lib64/libc.so.6', |
|
|
|
'/lib/libc.musl-i386.so.1', # Alpine |
|
|
@ -23,18 +23,18 @@ libcPaths = [ |
|
|
|
] |
|
|
|
|
|
|
|
libcPath = None |
|
|
|
for libc in libcPaths: |
|
|
|
if os.path.exists(libc): # try to locate libc.so |
|
|
|
libcPath = libc |
|
|
|
for path in libcSysPaths: |
|
|
|
if os.path.exists(path): # try to locate libc.so |
|
|
|
libcPath = path |
|
|
|
break |
|
|
|
if libcPath is None: # lost libc.so -> unable to utilize prctl |
|
|
|
logging.warning('libc.so not found') |
|
|
|
logger.warning('libc.so not found') |
|
|
|
else: |
|
|
|
logging.info('libc.so -> ' + str(libcPath)) |
|
|
|
logger.info('libc.so -> %s' % libcPath) |
|
|
|
|
|
|
|
|
|
|
|
class Process(object): |
|
|
|
""" Manage a sub process and it's file. |
|
|
|
""" Manage a sub process and it's configure file. |
|
|
|
|
|
|
|
Arguments: |
|
|
|
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 |
|
|
|
if os.path.isdir(self.workDir): |
|
|
|
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: |
|
|
|
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: |
|
|
|
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: |
|
|
|
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 |
|
|
|
|
|
|
|
def __killProcess(self, killSignal: int) -> None: |
|
|
|
try: |
|
|
|
pgid = os.getpgid(self.__process.pid) # progress group id |
|
|
|
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: |
|
|
|
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: |
|
|
|
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 |
|
|
|
try: |
|
|
|
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: |
|
|
|
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, |
|
|
|
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.file = copy.deepcopy([file] if type(file) == dict else file) # depth = 2 |
|
|
|
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 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 |
|
|
|
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: |
|
|
|
self.start() |
|
|
|
|
|
|
|
def setCmd(self, cmd: str or list) -> None: |
|
|
|
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: |
|
|
|
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: |
|
|
|
self.file = copy.deepcopy([file] if type(file) == dict else file) |
|
|
|
if self.file is None: |
|
|
|
logging.info('[%s] Process setting file -> None' % self.id) |
|
|
|
logger.info('[%s] Process setting file -> None' % self.id) |
|
|
|
return |
|
|
|
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: |
|
|
|
self.__capture = isCapture |
|
|
|
logging.debug('[%s] Process ready to start (%s)' % ( |
|
|
|
self.id, ('with' if self.__capture else 'without') + ' output capture' |
|
|
|
logger.debug('[%s] Process ready to start (%s output capture)' % ( |
|
|
|
self.id, 'with' if self.__capture else 'without' |
|
|
|
)) |
|
|
|
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') |
|
|
|
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') |
|
|
|
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 |
|
|
|
for file in self.file: |
|
|
|
with open(file['path'], 'w', encoding = 'utf-8') as fileObject: # save 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 |
|
|
|
self.__logfile = os.path.join(self.workDir, self.id + '.log') |
|
|
|
logging.debug('[%s] Process output capture -> %s' % (self.id, self.__logfile)) |
|
|
|
self.__logfile = os.path.join(self.workDir, '%s.log' % self.id) |
|
|
|
logger.debug('[%s] Process output capture -> %s' % (self.id, self.__logfile)) |
|
|
|
stdout = open(self.__logfile, 'w', encoding = 'utf-8') |
|
|
|
stderr = STDOUT # combine the stderr with stdout |
|
|
|
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 |
|
|
|
) |
|
|
|
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') |
|
|
|
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 |
|
|
|
try: |
|
|
|
signalName = signal.Signals(signalNum).name |
|
|
|
except: |
|
|
|
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) |
|
|
|
|
|
|
|
def status(self) -> bool: # check if the sub process is still running |
|
|
|
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 |
|
|
|
|
|
|
|
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: |
|
|
|
self.__process.wait(timeout = timeout) |
|
|
|
logging.info('[%s] Process wait timeout -> exit' % self.id) |
|
|
|
logger.info('[%s] Process wait timeout -> exit' % self.id) |
|
|
|
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 |
|
|
|
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) |
|
|
|
time.sleep(waitTime / 1000) # sleep (ms -> s) |
|
|
|
while self.__process.poll() is None: # confirm sub process exit |
|
|
|
self.__killProcess(killSignal) |
|
|
|
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: |
|
|
|
try: |
|
|
|
with open(self.__logfile, 'r', encoding = 'utf-8') as fileObject: # read sub process output |
|
|
|
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) |
|
|
|
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 |
|
|
|
for file in self.file: |
|
|
|
self.__deleteFile(file['path']) |