def process_dict_input(dictionary): # vars. expect = list(dictionary.keys()) send = list(dictionary.values()) # expect . if log_level >= 8: print(f"Expecting one of the following inputs: {expect}.") response = spawn.expect(expect=expect, send=send, timeout=timeout) if not response.success: if "None of the specified inputs were expected." in response.error: if optional: return Response.error( f"Unable to find the expected input but still success since it is optional." ) else: return Response.error( f"Specified input [{Array(expect).string(joiner=', ')}] was not expected." ) else: return response if log_level >= 8: print("Send response message:", response.message) # success. return Response.success("Success.")
def send_crash(self, # all optional. # option 1: the success message. message=None, # (1) args={}, # (2) # option 2: the error message. error=None, # option 3: the response object. response=None, # save the message/error/response. save=False, # the active log level (int) (leave None to use self.log_level). log_level=None, # the required log level for when to print to console (leave None to use Response.log_level ; default: 0). required_log_level=Response.log_level, ): if log_level == None: log_level = self.log_level if isinstance(message, (str,String)): self.__response__ = Response.success(message, args, log_level=-1, save=False) msg = message elif isinstance(error, (str,String)): self.__response__ = Response.error(error, log_level=-1, save=False) msg = error elif isinstance(response, ResponseObject): self.__response__ = response if response["success"]: msg = response["message"] else: msg = response["error"] else: raise Exceptions.InvalidUsage("Define one of the following parameters: [message:str, error:str, response:ResponseObject].") self.__status__ = "crashed" if log_level >= required_log_level or save: Response.log(response=self.__response__, save=save, log_level=log_level, required_log_level=required_log_level) return self.__response__
def crashed(self): response = self.read(wait=False) if not response.success: return response if self.exit_status not in [0, None]: return Response.error( f"{self.response_str} returned exit status: [{self.exit_status}] (output: {self.read(wait=False, __safe__=True).output})." ) return Response.success(f"{self.response_str} is not crashed.")
def safe_start(self, timeout=120, sleeptime=1): self.log(f"Stopping thread {self.id}.") self.start() for i in range(int(timeout/sleeptime)): if self.running: break time.sleep(sleeptime) if not self.running: return Response.error(f"Unable to start thread {self}.", log_level=self.log_level, required_log_level=0) return Response.success(f"Successfully started thread {self.id}.", log_level=self.log_level, required_log_level=0)
def start(self): try: self.child = pexpect.spawn(self.command) except Exception as e: return Response.error( f"Failed to spawn {self.response_str}, error: {e}.") # check exit status. if self.child.exitstatus not in [0, None]: return OutputObject( error= f"{self.response_str} returned exit status: [{spawn.child.exitstatus}] (output: {self.read(wait=False, __safe__=True).output})." ) self.__output__ = "" return Response.success(f"Successfully spawned {self.response_str}.")
def kill(self): # handle output. killed = None try: #if self.async_: # g = yield from self.child.terminate(force=True, async_=True) # killed = next(x) #else: killed = self.child.terminate(force=True) except Exception as e: return Response.error( f"Failed to kill process [{self.command}], error: {e}.") if killed == None: return Response.error( f"Failed to kill process [{self.command}] (#452983).") if not killed: return Response.error(f"Unable to kill process [{self.command}].") else: return Response.success( f"Successfully killed process [{self.command}].")
def safe_stop(self, timeout=120, sleeptime=1): self.log(f"Stopping thread {self.id}.") self.send_stop() for i in range(int(timeout/sleeptime)): if self.stopped: break time.sleep(sleeptime) if not self.stopped: return Response.error(f"Unable to stop thread {self}.") found = False try: response = self.stop() found = True except AttributeError: response = None if not found: try: response = self.__stop__() found = True except AttributeError: response = None if isinstance(response, ResponseObject) and not response.success: return response return Response.success(f"Successfully stopped thread {self.id}.", required_log_level=0)
def log(self, # option 1: # the message (#1 param). message=None, # option 2: # the error. error=None, # option 3: # the response dict (leave message None to use). response={}, # print the response as json. json=JSON, # optionals: # the active log level (leave None to use self.log_level). log_level=None, # the required log level for when printed to console. required_log_level=0, # save to log file. save=False, # save errors always (for options 2 & 3 only). save_errors=None, # the log mode (leave None for default). mode=None, ): if log_level == None: log_level = self.log_level return Response.log( # option 1: # the message (#1 param). message=message, # option 2: # the error. error=error, # option 3: # the response dict (leave message None to use). response=response, # print the response as json. json=json, # optionals: # the active log level. log_level=log_level, # the required log level for when printed to console (leave None to use self.log_level). required_log_level=required_log_level, # save to log file. save=save, # save errors always (for options 2 & 3 only). save_errors=save_errors, # the log mode (leave None for default). mode=mode, )
def expect( self, # the expected data parameter (#1). # str instantce: expect a single identifier. # list instance: expect one of the provided identifiers & return the found one if success. expect=["Password*"], # the optional data to send (#2). # none instance: do not send anything. # str instance: the data to send. # list/tuple instance: send value of index from expected expect (required expect to be a list, Array & the indexes of [expect, send] be match). send=None, # the timeout (float). timeout=1.0, ): # check none timeout. if timeout == None: timeout = 1.0 # single expect. if isinstance(expect, str): if isinstance(send, (list, Array)): raise Exceptions.InvalidUsage( f"{self.__traceback__(function='expect', parameter='send')}: parameter [send] requires to be be a [str, String] when parameter [expect] is a [{expect.__class__.__name__}], not [{send.__class__.__name__}]." ) """ add to lines to output (before) (adds nothing when empty) (do not use self.expecting before adding the output). """ response = self.read(wait=False) if not response.success: return response """ check expect & send if provided. """ c = 1 try: #if self.async_: # g = yield from self.child.expect(expect, timeout=timeout, async_=True) # r = next(g) #else: r = self.child.expect(expect, timeout=timeout) if not isinstance(r, (int, Integer, float)): return Response.error( f"Expected [{expect}] is not the child's expected input (#873465)." ) c = 2 if self.log_level >= 8: print(f"Found expected: {expect}") if send != None: if self.log_level >= 8: print(f"Attempting to send: {send}") #if self.async_: # g = yield from self.child.sendline(str(send), async_=True) # r = next(g) #else: r = self.child.sendline(str(send)) self.__secrets__.append(str(send)) if self.log_level >= 8: print(f"Succesfully sended: {send}") except pexpect.exceptions.TIMEOUT: if c == 1: return Response.error( f"Expected [{expect}] is not the child's expected input, error: Timeout (during epxecting)." ) else: return Response.error( f"Failed to send expected [{expect}], error: Timeout (during sending input)." ) except pexpect.exceptions.EOF: if c == 1: return Response.error( f"Expected [{expect}] is not the child's expected input, error: End of file (during epxecting)." ) else: return Response.error( f"Failed to send expected [{expect}], error: End of file (during sending input)." ) except Exception as e: if c == 1: return Response.error( f"Expected [{expect}] is not the child's expected input, error: {e}." ) else: return Response.error( f"Failed to send expected [{expect}], error: {e}.") """ add to lines to output (after) (adds nothing when empty) (do not use self.expecting before adding the output). """ response = self.read(wait=False) if not response.success: return response # handler. if send != None: return Response.success( f"Successfully send expected input ({expect}).") else: return Response.success( f"Successfully retrieved expected input ({expect}).") # list expect. elif isinstance(expect, (list, Array)): index = 0 for _expect_ in expect: if isinstance(send, (list, Array)): try: _send_ = str(send[index]) except: raise Exceptions.InvalidUsage( f"{self.__traceback__(function='expect', parameter='send')}: parameter [send] and parameter [expect] do not have the same indexes." ) else: _send_ = str(send) if self.log_level >= 8: print(f"Checking optional expect: {_expect_}") response = self.expect(expect=_expect_, timeout=timeout, send=_send_) if not response.success and "is not the child's expected input" not in response.error: return response elif response.success: return Response.success( f"Successfully {Boolean(send).string(true='send', false='retrieved')} the expected input(s).", { "expected": _expect_, "index": index, }) index += 1 return Response.error(f"None of the specified input was expected.") # invalid usage. else: raise Exceptions.InvalidUsage( f"{self.__traceback__(function='expect', parameter='expect')}: parameter [expect] requires to be be a [Dictionary], not [{config.__class__.__name__}]." )
def execute( # Notes: # returns a syst3m.console.OutputObject object (very similair to ResponseObject). # # Mode: # option 1: # the command in str format, the command is saved to a script & then executed). command="ls .", # joiner for when command is in list format. joiner=" ", # option 2: the path to script. path=None, # # Executement: # the executable. executable="sh", # the arguments passed to the (saved) script. arguments=[], # # Options: # asynchronous process. async_=False, # await asynchronous child (sync process always awaits). wait=False, # kill process when finished (async that is not awaited is never killed). kill=True, # the subprocess shell parameter. shell=False, # serialize output to dict (expect literal dictionary / json output). serialize=False, # # Input (sync only): # send input to the command. # undefined: send no input & automatically await the process since input is always sync. # dict instance: selects "and" mode ; send expected inputs and their value & return error when one of them is missing. # list[dict] instance: send all dictionaries in the list (default dict behaviour so one of the keys in each dict is expected). input=None, # the input timeout (float) (list with floats by index from input) timeout=1.0, # do not throw an error when the input is missing or not expected when optional is disabled (bool). optional=False, # # Logging. # the log level. log_level=Defaults.options.log_level, # # System functions. # add additional attributes to the spawn object. __spawn_attributes__={}, # ): # checks, if input != None and not isinstance(input, (dict, Dictionary, list, Array)): raise Exceptions.InvalidUsage( f"<Code.execute>: Parameter [input] requires to be be a [dict, Dictionary, list, Array], not [{iput.__class__.__name__}]." ) # vars. delete = False if path == None: delete = True path = f"/tmp/tmp_script_{String('').generate()}" if isinstance(command, list): command = Array(array=command).string(joiner=joiner) Files.save(path, command) response_str = f"command ({command})" else: response_str = f"script ({path})" # execute with input. #if isinstance(input, (dict, Dictionary, list, Array)): # checks. #if async_: # raise Exceptions.InvalidUsage(f"<Code.execute>: Parameters [input] & [async] are not compatible, select either one.") # spawn. l = [] for i in arguments: l.append(f'"{i}"') arguments = Array(l).string(joiner=' ') if log_level >= 8: print(f"Spawn: [ $ {executable} {path} {arguments}]", ) spawn = Spawn( command=f"{executable} {path} {arguments}", #async_=async_, # does not work. response_str=response_str, log_level=log_level, attributes=__spawn_attributes__, ) spawn.echo = False if isinstance(timeout, (int, float, Integer)): spawn.timeout = int(timeout) # start. response = spawn.start() if not response.success: return OutputObject( error=f"Failed to start {response_str}, error: {response.error}", log_level=log_level) # check crashed response = spawn.crashed() if not response.success: return OutputObject(error=response.error, log_level=log_level) # has already exited. elif spawn.exit_status in [0]: # get output. response = spawn.wait(timeout=1.0) if not response.success: return OutputObject(error=response.error, log_level=log_level) # exit status. output = response.output # # proceed if already finished. elif spawn.exit_status not in [0]: # await. for i in range(10): if spawn.running: break time.sleep(1) if not spawn.running: return OutputObject(error=f"Unable to start {response_str}.", log_level=log_level) # str input. if isinstance(input, (list, Array)): if len(input) > 0: str_input = Array(list(input[0].keys())).string(joiner=", ") else: str_input = Array(input).string(joiner=", ") elif isinstance(input, (dict, Dictionary)): str_input = Array(list(input.keys())).string(joiner=", ") else: str_input = f"[{input}]" # send optional input. error_end_of_file = None if not async_ and isinstance( input, (list, Array, dict, Dictionary)) and len(input) > 0: # expect one of the keys in the dictionary. def process_dict_input(dictionary): # vars. expect = list(dictionary.keys()) send = list(dictionary.values()) # expect . if log_level >= 8: print(f"Expecting one of the following inputs: {expect}.") response = spawn.expect(expect=expect, send=send, timeout=timeout) if not response.success: if "None of the specified inputs were expected." in response.error: if optional: return Response.error( f"Unable to find the expected input but still success since it is optional." ) else: return Response.error( f"Specified input [{Array(expect).string(joiner=', ')}] was not expected." ) else: return response if log_level >= 8: print("Send response message:", response.message) # success. return Response.success("Success.") """ check expecting. """ expecting = True if not spawn.expecting: expecting = False if not optional: if log_level >= 1: return OutputObject( error= f"Failed to send expected input {str_input} to {response_str}, child is not expecting any input [{spawn.child}].", log_level=log_level) else: return OutputObject( error= f"Failed to send expected input {str_input} to {response_str}, child is not expecting any input.", log_level=log_level) # limit not expecting by optional. if expecting: # send all dicts in the list (list instance). error_end_of_file = False if isinstance(input, (list, Array)): for _input_ in input: response = process_dict_input(_input_) if not response.success: if "End of file" in response.error: error_end_of_file = True else: # str input. if isinstance(_input_, (list, Array)): str_input = Array(_input_).string( joiner=", ") elif isinstance(_input_, (dict, Dictionary)): str_input = Array(list( _input_.keys())).string(joiner=", ") else: str_input = f"[{_input_}]" if optional: break else: return OutputObject( error= f"Failed to send one of the expected input(s) {str_input}, error: {response.error}", log_level=log_level) # send one of the keys (dict instance). elif isinstance(input, (dict, Dictionary)): response = process_dict_input(input) if not response.success: if "End of file" in response.error: error_end_of_file = True elif not optional: return OutputObject( error= f"Failed to send one of the expected input(s) {str_input}, error: {response.error}", log_level=log_level) """ check no input left (does not work properly). if not error_end_of_file and spawn.expecting: try: after = spawn.child.after.decode() except : after = spawn.child.after return OutputObject(error=f"Failed to execute {response_str}, still expecting: [{after}].", log_level=log_level) """ # do not get or kill when async. output = None if not async_: # check crashed. response = spawn.crashed() if not response.success: return OutputObject(error=response.error, log_level=log_level) # always await sync. response = spawn.wait() if not response.success: return OutputObject(error=response.error, log_level=log_level) output = response.output if error_end_of_file != None and error_end_of_file: if log_level >= 1: return OutputObject( error= f"Failed to send expected input {str_input} to {response_str} (#234343) (output: {output}) (child: {spawn.child}).", log_level=log_level) else: return OutputObject( error= f"Failed to send expected input {str_input} to {response_str} (#234343) (output: {output}).", log_level=log_level) # check kill. if kill and spawn.running: if log_level >= 8: print(f"Killing process {response_str}.") response = spawn.kill() if not response.success: return OutputObject( error= f"Failed to kill {response_str}, error: {response.error}", log_level=log_level) # async. elif async_: # check exit status. response = spawn.read(wait=False) if not response.success: if not response.success: return OutputObject( error= f"Failed to retrieve output from spawn {response_str}, error: {response.error}", log_level=log_level) if spawn.child.exitstatus not in [0, None]: return OutputObject( error= f"{response_str} returned exit status: [{spawn.child.exitstatus}] (output: {self.read(wait=False, __safe__=True).output}).", log_level=log_level) # await async. if wait: # await. response = spawn.wait() if not response.success: return OutputObject(error=response.error, log_level=log_level) # exit status. output = response.output if spawn.child.exitstatus not in [0, None]: return OutputObject( error= f"{response_str} returned exit status: [{spawn.child.exitstatus}] (output: {self.read(wait=False, __safe__=True).output}).", log_level=log_level) # check kill. if kill and spawn.running: if log_level >= 8: print(f"Killing process {response_str}.") response = spawn.kill() if not response.success: return OutputObject( error= f"Failed to kill {response_str}, error: {response.error}", log_level=log_level) # handler. if delete: Files.delete(path) if serialize: try: response = Response.ResponseObject(json=output) except Exception as e: if loader != None: loader.stop(success=False) return OutputObject( error=f"Failed to serialize (output: {output}).", log_level=log_level) if not response.success: return OutputObject( error= f"Encoutered an error in the serialized response, error: {response.error}", log_level=log_level) return OutputObject(message=f"Succesfully executed {response_str}.", log_level=log_level, attributes={ "output": output, "process": spawn, "pid": spawn.child.pid, "running": spawn.running, "exit_status": spawn.exit_status, }) """
def response(self): return Response.response(self.dict())
def read( self, # with await False it reads only the printed output regardless the status & never throws timeout. wait=False, # the timeout, leave None for no timeout. timeout=None, # the live boolean (bool) (prints live logs to console when enabled) (leave None to use self.log_level >= 1). live=None, # system variables. # safe True always a response.output variable upon error the response.output is "". __safe__=False, ): # clean line. def clean_line(line): if isinstance(line, bytes): line = line.decode() if line in [" password:"******"", " ", " ", " ", " " ] and len(line) >= len(strip_last) and line[ -len(strip_last):] == strip_last: return line[:-len(strip_last)] return line def clean_output(output): _output_, b = "", output.replace("\r", "").replace("\t", "").split("\n") c, m = 0, len(b) - 1 for line in b: l = clean_line(line) if l not in [None]: if c < m: _output_ += l + "\n" else: _output_ += l c += 1 return _output_ def log_output(output): while True: if len(output) >= 1 and output[len(output) - 1] == "\n": output = output[:-1] else: break print(output) # checks. if live == None: live = self.log_level >= 1 # old timeout. old = self.child.timeout # version 2: reads only the printed lines and stops when the timeout is hit so there is no timeout required. output_logs_allowed = True if not wait: self.child.timeout = 0.25 output_logs_allowed = False output, c = "", 0 for line in range(1000000000): try: #if self.async_: # g = yield from self.child.readline(line, async_=True) # output += next(g).decode() #else: new = self.child.readline(line).decode() if new == "": c += 1 if c >= 100: break else: output_logs_allowed = True c = 0 output += new except pexpect.exceptions.TIMEOUT: break except pexpect.exceptions.EOF: break except Exception as e: if timeout != None: self.child.timeout = old e = str(e) if self.log_level <= 0: e = str(e).split( "\n" )[0] # strip the full child from the pexpect error message. while True: if len(e) >= 1 and e[len(e) - 1] == ".": e = e[:-1] else: break if "Timeout" in str(e): response = Response.error( f"{e} (most likely the command is still expecting input)." ) if __safe__: response.output = "" return response else: response = Response.error(f"{e}.") if __safe__: response.output = "" return response # version 1: throws an error when unable to read output if timeout is defined, so it can be used to detect if input is expected. else: # handle output. self.child.timeout = timeout try: #output = self.child.read().decode() output = "" #if self.async_: # g = yield from self.child.readlines(async_=True) # lines = next(x) #else: lines = self.child.readlines() if lines == []: output_logs_allowed = False else: for i in lines: output += i.decode() except Exception as e: self.child.timeout = old e = str(e) if self.log_level <= 0: e = str(e).split( "\n" )[0] # strip the full child from the pexpect error message. while True: if len(e) >= 1 and e[len(e) - 1] == ".": e = e[:-1] else: break if "Timeout" in str(e): response = Response.error( f"{e} (most likely the command is still expecting input)." ) if __safe__: response.output = "" return response else: response = Response.error(f"{e}.") if __safe__: response.output = "" return response # clean output. output = clean_output(output) if output_logs_allowed and live: log_output(output) # handler. self.child.timeout = old self.__output__ += output self.__output__ = clean_output(self.__output__) return Response.success("Successfully retrieved the child's output.", { "output": str(self.__output__), "new_output": output, })