From 81459efcd609b34820ff391a20b5a5cef1476488 Mon Sep 17 00:00:00 2001 From: Aaron Heise Date: Sat, 11 Feb 2023 07:38:35 -0600 Subject: [PATCH] Address several code style concerns --- .gitignore | 1 + README.md | 2 +- pyproject.toml | 7 +++ rnsh/__version.py | 2 +- rnsh/exception.py | 34 ++++++++++++ rnsh/process.py | 97 ++++++++++++++++---------------- rnsh/retry.py | 15 ++--- rnsh/rnsh.py | 132 ++++++++++++++++++++++---------------------- rnsh/rnslogging.py | 48 ++++++++-------- rnsh/testlogging.py | 5 +- 10 files changed, 196 insertions(+), 147 deletions(-) create mode 100644 rnsh/exception.py diff --git a/.gitignore b/.gitignore index e771163..896e771 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ testconfig/ /rnsh.egg-info/ /build/ /dist/ +.pytest_cache/ \ No newline at end of file diff --git a/README.md b/README.md index 883264d..a2dd086 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ out. ## Quickstart -Requires Python 3.11+ on Linux or Unix. WSL probably works. Cygwin might work, too. +Requires Python 3.10+ on Linux or Unix. WSL probably works. Cygwin might work, too. - Activate a virtualenv - `pip3 install rnsh` diff --git a/pyproject.toml b/pyproject.toml index d7220fe..67379f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,13 @@ psutil = "^5.9.4" rnsh = 'rnsh.rnsh:rnsh_cli' +[tool.poetry.group.dev.dependencies] +pytest = "^7.2.1" +flake8 = "^6.0.0" +bandit = "^1.7.4" +isort = "^5.12.0" +safety = "^2.3.5" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/rnsh/__version.py b/rnsh/__version.py index 0653d59..c3254b9 100644 --- a/rnsh/__version.py +++ b/rnsh/__version.py @@ -20,4 +20,4 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -__version__ = "0.0.1" \ No newline at end of file +__version__ = "0.0.1" diff --git a/rnsh/exception.py b/rnsh/exception.py new file mode 100644 index 0000000..e7d8043 --- /dev/null +++ b/rnsh/exception.py @@ -0,0 +1,34 @@ +from contextlib import AbstractContextManager + + +class permit(AbstractContextManager): + """Context manager to allow specified exceptions + + The specified exceptions will be allowed to bubble up. Other + exceptions are suppressed. + + After a non-matching exception is suppressed, execution proceeds + with the next statement following the with statement. + + with allow(KeyboardInterrupt): + time.sleep(300) + # Execution still resumes here if no KeyboardInterrupt + """ + + def __init__(self, *exceptions): + self._exceptions = exceptions + + def __enter__(self): + pass + + def __exit__(self, exctype, excinst, exctb): + # Unlike isinstance and issubclass, CPython exception handling + # currently only looks at the concrete type hierarchy (ignoring + # the instance and subclass checking hooks). While Guido considers + # that a bug rather than a feature, it's a fairly hard one to fix + # due to various internal implementation details. suppress provides + # the simpler issubclass based semantics, rather than trying to + # exactly reproduce the limitations of the CPython interpreter. + # + # See http://bugs.python.org/issue12029 for more details + return exctype is not None and issubclass(exctype, self._exceptions) diff --git a/rnsh/process.py b/rnsh/process.py index f112e27..a7097d3 100644 --- a/rnsh/process.py +++ b/rnsh/process.py @@ -23,24 +23,27 @@ import asyncio import contextlib import errno +import fcntl import functools -import re +import logging as __logging +import os +import pty +import select import signal import struct +import sys +import termios import threading import tty -import pty -import os -import asyncio -import sys -import fcntl + import psutil -import select -import termios -import logging as __logging + +import rnsh.exception as exception + module_logger = __logging.getLogger(__name__) -def tty_add_reader_callback(fd: int, callback: callable, loop: asyncio.AbstractEventLoop | None = None): + +def tty_add_reader_callback(fd: int, callback: callable, loop: asyncio.AbstractEventLoop = None): """ Add an async reader callback for a tty file descriptor. @@ -60,7 +63,8 @@ def tty_add_reader_callback(fd: int, callback: callable, loop: asyncio.AbstractE loop = asyncio.get_running_loop() loop.add_reader(fd, callback) -def tty_read(fd: int) -> bytes | None: + +def tty_read(fd: int) -> bytes: """ Read available bytes from a tty file descriptor. When used in a callback added to a file descriptor using tty_add_reader_callback(...), this function creates a solution for non-blocking reads from ttys. @@ -78,7 +82,7 @@ def tty_read(fd: int) -> bytes | None: break for f in ready: try: - data = os.read(fd, 512) + data = os.read(f, 512) except OSError as e: if e.errno != errno.EIO and e.errno != errno.EWOULDBLOCK: raise @@ -89,6 +93,7 @@ def tty_read(fd: int) -> bytes | None: result.extend(data) return result + def fd_is_closed(fd: int) -> bool: """ Check if file descriptor is closed @@ -100,20 +105,20 @@ def fd_is_closed(fd: int) -> bool: except OSError as ose: return ose.errno == errno.EBADF -def tty_unset_reader_callbacks(fd: int, loop: asyncio.AbstractEventLoop | None = None): + +def tty_unset_reader_callbacks(fd: int, loop: asyncio.AbstractEventLoop = None): """ Remove async reader callbacks for file descriptor. :param fd: file descriptor :param loop: asyncio event loop from which to remove callbacks """ - try: + with exception.permit(SystemExit): if loop is None: loop = asyncio.get_running_loop() loop.remove_reader(fd) - except: - pass -def tty_get_winsize(fd: int) -> [int, int, int , int]: + +def tty_get_winsize(fd: int) -> [int, int, int, int]: """ Ge the window size of a tty. :param fd: file descriptor of tty @@ -123,6 +128,7 @@ def tty_get_winsize(fd: int) -> [int, int, int , int]: rows, cols, h_pixels, v_pixels = struct.unpack('HHHH', packed) return rows, cols, h_pixels, v_pixels + def tty_set_winsize(fd: int, rows: int, cols: int, h_pixels: int, v_pixels: int): """ Set the window size on a tty. @@ -137,6 +143,7 @@ def tty_set_winsize(fd: int, rows: int, cols: int, h_pixels: int, v_pixels: int) packed = struct.pack('HHHH', rows, cols, h_pixels, v_pixels) fcntl.ioctl(fd, termios.TIOCSWINSZ, packed) + def process_exists(pid) -> bool: """ Check For the existence of a unix pid. @@ -150,6 +157,7 @@ def process_exists(pid) -> bool: else: return True + class TtyRestorer: def __init__(self, fd: int): """ @@ -189,12 +197,12 @@ class CallbackSubprocess: # time between checks of child process PROCESS_POLL_TIME: float = 0.1 - def __init__(self, argv: [str], env: dict | None, loop: asyncio.AbstractEventLoop, stdout_callback: callable, + def __init__(self, argv: [str], env: dict, loop: asyncio.AbstractEventLoop, stdout_callback: callable, terminated_callback: callable): """ Fork a child process and generate callbacks with output from the process. :param argv: the command line, tokenized. The first element must be the absolute path to an executable file. - :param term: the value that should be set for TERM. If None, the value from the parent process will be used + :param env: environment variables to override :param loop: the asyncio event loop to use :param stdout_callback: callback for data, e.g. def callback(data:bytes) -> None :param terminated_callback: callback for termination/return code, e.g. def callback(return_code:int) -> None @@ -210,9 +218,9 @@ class CallbackSubprocess: self._loop = loop self._stdout_cb = stdout_callback self._terminated_cb = terminated_callback - self._pid: int | None = None - self._child_fd: int | None = None - self._return_code: int | None = None + self._pid: int = None + self._child_fd: int = None + self._return_code: int = None def terminate(self, kill_delay: float = 1.0): """ @@ -223,19 +231,15 @@ class CallbackSubprocess: if not self.running: return - try: + with exception.permit(SystemExit): os.kill(self._pid, signal.SIGTERM) - except: - pass def kill(): if process_exists(self._pid): self._log.debug("kill()") - try: + with exception.permit(SystemExit): os.kill(self._pid, signal.SIGHUP) os.kill(self._pid, signal.SIGKILL) - except: - pass self._loop.call_later(kill_delay, kill) @@ -280,15 +284,15 @@ class CallbackSubprocess: self._log.debug(f"set_winsize({r},{c},{h},{v}") tty_set_winsize(self._child_fd, r, c, h, v) - def copy_winsize(self, fromfd:int): + def copy_winsize(self, fromfd: int): """ Copy window size from one tty to another. :param fromfd: source tty file descriptor """ - r,c,h,v = tty_get_winsize(fromfd) - self.set_winsize(r,c,h,v) + r, c, h, v = tty_get_winsize(fromfd) + self.set_winsize(r, c, h, v) - def tcsetattr(self, when: int, attr: list[int | list[int | bytes]]): + def tcsetattr(self, when: int, attr: list[any]): # actual type is list[int | list[int | bytes]] """ Set tty attributes. :param when: when to apply change: termios.TCSANOW or termios.TCSADRAIN or termios.TCSAFLUSH @@ -296,7 +300,7 @@ class CallbackSubprocess: """ termios.tcsetattr(self._child_fd, when, attr) - def tcgetattr(self) -> list[int | list[int | bytes]]: + def tcgetattr(self) -> list[any]: # actual type is list[int | list[int | bytes]] """ Get tty attributes. :return: tty attributes value @@ -328,7 +332,6 @@ class CallbackSubprocess: # env["SHELL"] = program # self._log.debug(f"set login shell {self._command}") - self._pid, self._child_fd = pty.fork() if self._pid == 0: @@ -341,10 +344,8 @@ class CallbackSubprocess: for c in p.connections(kind='all'): if c == sys.stdin.fileno() or c == sys.stdout.fileno() or c == sys.stderr.fileno(): continue - try: + with exception.permit(SystemExit): os.close(c.fd) - except: - pass os.setpgrp() os.execvpe(program, self._command, env) except Exception as err: @@ -364,21 +365,19 @@ class CallbackSubprocess: except Exception as e: if not hasattr(e, "errno") or e.errno != errno.ECHILD: self._log.debug(f"Error in process poll: {e}") + self._loop.call_later(CallbackSubprocess.PROCESS_POLL_TIME, poll) def reader(fd: int, callback: callable): - result = bytearray() - try: - c = tty_read(fd) - if c is not None and len(c) > 0: - callback(c) - except: - pass + with exception.permit(SystemExit): + data = tty_read(fd) + if data is not None and len(data) > 0: + callback(data) tty_add_reader_callback(self._child_fd, functools.partial(reader, self._child_fd, self._stdout_cb), self._loop) @property - def return_code(self) -> int | None: + def return_code(self) -> int: return self._return_code @@ -387,7 +386,6 @@ async def main(): A test driver for the CallbackProcess class. python ./process.py /bin/zsh --login """ - import rnsh.testlogging log = module_logger.getChild("main") if len(sys.argv) <= 1: @@ -413,14 +411,14 @@ async def main(): stdout_callback=stdout, terminated_callback=terminated) - def sigint_handler(signal, frame): + def sigint_handler(sig, frame): # log.debug("KeyboardInterrupt") if process is None or process.started and not process.running: raise KeyboardInterrupt elif process.running: process.write("\x03".encode("utf-8")) - def sigwinch_handler(signal, frame): + def sigwinch_handler(sig, frame): # log.debug("WindowChanged") process.copy_winsize(sys.stdin.fileno()) @@ -443,6 +441,7 @@ async def main(): log.debug(f"got retcode {val}") return val + if __name__ == "__main__": tr = TtyRestorer(sys.stdin.fileno()) try: @@ -450,4 +449,4 @@ if __name__ == "__main__": asyncio.run(main()) finally: tty_unset_reader_callbacks(sys.stdin.fileno()) - tr.restore() \ No newline at end of file + tr.restore() diff --git a/rnsh/retry.py b/rnsh/retry.py index f6f8f52..4b8e738 100644 --- a/rnsh/retry.py +++ b/rnsh/retry.py @@ -24,6 +24,7 @@ import asyncio import logging import threading import time +import rnsh.exception as exception import logging as __logging from typing import Callable @@ -47,7 +48,9 @@ class RetryStatus: @property def ready(self): ready = time.time() > self.try_time + self.wait_delay - # self._log.debug(f"ready check {self.tag} try_time {self.try_time} wait_delay {self.wait_delay} next_try {self.try_time + self.wait_delay} now {time.time()} exceeded {time.time() - self.try_time - self.wait_delay} ready {ready}") + self._log.debug(f"ready check {self.tag} try_time {self.try_time} wait_delay {self.wait_delay} " + + f"next_try {self.try_time + self.wait_delay} now {time.time()} " + + f"exceeded {time.time() - self.try_time - self.wait_delay} ready {ready}") return ready @property @@ -72,11 +75,11 @@ class RetryThread: self._tag_counter = 0 self._lock = threading.RLock() self._run = True - self._finished: asyncio.Future | None = None + self._finished: asyncio.Future = None self._thread = threading.Thread(name=name, target=self._thread_run) self._thread.start() - def close(self, loop: asyncio.AbstractEventLoop | None = None) -> asyncio.Future | None: + def close(self, loop: asyncio.AbstractEventLoop = None) -> asyncio.Future: self._log.debug("stopping timer thread") if loop is None: self._run = False @@ -110,10 +113,8 @@ class RetryThread: with self._lock: for retry in prune: self._log.debug(f"pruned retry {retry.tag}, retry count {retry.tries}/{retry.try_limit}") - try: + with exception.permit(SystemExit): self._statuses.remove(retry) - except: - pass if self._finished is not None: self._finished.set_result(None) @@ -126,7 +127,7 @@ class RetryThread: return next(filter(lambda s: s.tag == tag, self._statuses), None) is not None def begin(self, try_limit: int, wait_delay: float, try_callback: Callable[[any, int], any], - timeout_callback: Callable[[any, int], None], tag: int | None = None) -> any: + timeout_callback: Callable[[any, int], None], tag: int = None) -> any: self._log.debug(f"running first try") tag = try_callback(tag, 1) self._log.debug(f"first try got id {tag}") diff --git a/rnsh/rnsh.py b/rnsh/rnsh.py index 2acd86e..f5f474b 100644 --- a/rnsh/rnsh.py +++ b/rnsh/rnsh.py @@ -23,22 +23,25 @@ # SOFTWARE. from __future__ import annotations -import functools -from typing import Callable, TypeVar -import termios -import rnsh.rnslogging as rnslogging -import RNS -import time -import sys -import os -import base64 -import rnsh.process as process + import asyncio -import threading -import signal -import rnsh.retry as retry -from rnsh.__version import __version__ +import base64 +import functools import logging as __logging +import os +import signal +import sys +import termios +import threading +import time +from typing import Callable, TypeVar + +import RNS +import rnsh.exception as exception +import rnsh.process as process +import rnsh.retry as retry +import rnsh.rnslogging as rnslogging +from rnsh.__version import __version__ module_logger = __logging.getLogger(__name__) @@ -60,11 +63,12 @@ _retry_timer = retry.RetryThread() _destination: RNS.Destination | None = None _loop: asyncio.AbstractEventLoop | None = None + async def _check_finished(timeout: float = 0): - await process.event_wait(_finished, timeout=timeout) + await process.event_wait(_finished, timeout=timeout) -def _sigint_handler(signal, frame): +def _sigint_handler(sig, frame): global _finished log = _get_logger("_sigint_handler") log.debug("SIGINT") @@ -91,7 +95,9 @@ def _prepare_identity(identity_path): _identity = RNS.Identity() _identity.to_file(identity_path) + def _print_identity(configdir, identitypath, service_name, include_destination: bool): + global _reticulum _reticulum = RNS.Reticulum(configdir=configdir, loglevel=RNS.LOG_INFO) _prepare_identity(identitypath) destination = RNS.Destination(_identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, service_name) @@ -121,12 +127,13 @@ async def _listen(configdir, command, identitypath=None, service_name="default", dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH // 8) * 2 if len(a) != dest_len: raise ValueError( - "Allowed destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format( + "Allowed destination length is invalid, must be {hex} hexadecimal " + + "characters ({byte} bytes).".format( hex=dest_len, byte=dest_len // 2)) try: destination_hash = bytes.fromhex(a) _allowed_identity_hashes.append(destination_hash) - except Exception as e: + except Exception: raise ValueError("Invalid destination entered. Check your input.") except Exception as e: log.error(str(e)) @@ -169,12 +176,10 @@ async def _listen(configdir, command, identitypath=None, service_name="default", except KeyboardInterrupt: log.warning("Shutting down") for link in list(_destination.links): - try: + with exception.permit(SystemExit): proc = ProcessState.get_for_tag(link.link_id) if proc is not None and proc.process.running: proc.process.terminate() - except: - pass await asyncio.sleep(1) links_still_active = list(filter(lambda l: l.status != RNS.Link.CLOSED, _destination.links)) for link in links_still_active: @@ -197,16 +202,11 @@ class ProcessState: cls.clear_tag(tag) cls._processes.append((tag, ps)) - @classmethod def clear_tag(cls, tag: any): with cls._lock: - try: + with exception.permit(SystemExit): cls._processes.remove(tag) - except: - pass - - def __init__(self, tag: any, @@ -222,8 +222,8 @@ class ProcessState: self._mdu = mdu self._loop = loop if loop is not None else asyncio.get_running_loop() self._process = process.CallbackSubprocess(argv=cmd, - env={ "TERM": term or os.environ.get("TERM", None), - "RNS_REMOTE_IDENTITY": remote_identity or ""}, + env={"TERM": term or os.environ.get("TERM", None), + "RNS_REMOTE_IDENTITY": remote_identity or ""}, loop=loop, stdout_callback=self._stdout_data, terminated_callback=terminated_callback) @@ -302,7 +302,6 @@ class ProcessState: except Exception as e: self._log.debug(f"failed to update winsz: {e}") - REQUEST_IDX_STDIN = 0 REQUEST_IDX_TERM = 1 REQUEST_IDX_TIOS = 2 @@ -327,9 +326,9 @@ class ProcessState: request[ProcessState.REQUEST_IDX_TERM] = os.environ.get("TERM", None) request[ProcessState.REQUEST_IDX_TIOS] = termios.tcgetattr(stdin_fd) request[ProcessState.REQUEST_IDX_ROWS], \ - request[ProcessState.REQUEST_IDX_COLS], \ - request[ProcessState.REQUEST_IDX_HPIX], \ - request[ProcessState.REQUEST_IDX_VPIX] = process.tty_get_winsize(stdin_fd) + request[ProcessState.REQUEST_IDX_COLS], \ + request[ProcessState.REQUEST_IDX_HPIX], \ + request[ProcessState.REQUEST_IDX_VPIX] = process.tty_get_winsize(stdin_fd) return request def process_request(self, data: [any], read_size: int) -> [any]: @@ -342,7 +341,7 @@ class ProcessState: # vpix = data[ProcessState.REQUEST_IDX_VPIX] # window vertical pixels # term_state = data[ProcessState.REQUEST_IDX_ROWS:ProcessState.REQUEST_IDX_VPIX+1] response = ProcessState.default_response() - term_state = data[ProcessState.REQUEST_IDX_TIOS:ProcessState.REQUEST_IDX_VPIX+1] + term_state = data[ProcessState.REQUEST_IDX_TIOS:ProcessState.REQUEST_IDX_VPIX + 1] response[ProcessState.RESPONSE_IDX_RUNNING] = self.process.running if self.process.running: @@ -365,17 +364,17 @@ class ProcessState: RESPONSE_IDX_RUNNING = 0 RESPONSE_IDX_RETCODE = 1 RESPONSE_IDX_RDYBYTE = 2 - RESPONSE_IDX_STDOUT = 3 + RESPONSE_IDX_STDOUT = 3 RESPONSE_IDX_TMSTAMP = 4 @staticmethod def default_response() -> [any]: response: list[any] = [ - False, # 0: Process running - None, # 1: Return value - 0, # 2: Number of outstanding bytes - None, # 3: Stdout/Stderr - None, # 4: Timestamp + False, # 0: Process running + None, # 1: Return value + 0, # 2: Number of outstanding bytes + None, # 3: Stdout/Stderr + None, # 4: Timestamp ].copy() response[ProcessState.RESPONSE_IDX_TMSTAMP] = time.time() return response @@ -406,7 +405,8 @@ def _subproc_data_ready(link: RNS.Link, chars_available: int): else: if not timeout: log.info( - f"Notifying client try {tries} (retcode: {process_state.return_code} chars avail: {chars_available})") + f"Notifying client try {tries} (retcode: {process_state.return_code} " + + f"chars avail: {chars_available})") packet = RNS.Packet(link, DATA_AVAIL_MSG.encode("utf-8")) packet.send() pr = packet.receipt @@ -431,6 +431,7 @@ def _subproc_data_ready(link: RNS.Link, chars_available: int): else: log.debug(f"Notification already pending for link {link}") + def _subproc_terminated(link: RNS.Link, return_code: int): global _loop log = _get_logger("_subproc_terminated") @@ -444,18 +445,20 @@ def _subproc_terminated(link: RNS.Link, return_code: int): def inner(): log.debug(f"cleanup culled link {link}") if link and link.status != RNS.Link.CLOSED: - try: - link.teardown() - except: - pass - finally: - ProcessState.clear_tag(link.link_id) + with exception.permit(SystemExit): + try: + link.teardown() + finally: + ProcessState.clear_tag(link.link_id) + _loop.call_later(300, inner) _loop.call_soon(_subproc_data_ready, link, 0) + _loop.call_soon_threadsafe(cleanup) -def _listen_start_proc(link: RNS.Link, remote_identity: str | None, term: str, loop: asyncio.AbstractEventLoop) -> ProcessState | None: +def _listen_start_proc(link: RNS.Link, remote_identity: str | None, term: str, + loop: asyncio.AbstractEventLoop) -> ProcessState | None: global _cmd log = _get_logger("_listen_start_proc") try: @@ -501,7 +504,7 @@ def _initiator_identified(link, identity): global _allow_all, _cmd, _loop log = _get_logger("_initiator_identified") log.info("Initiator of link " + str(link) + " identified as " + RNS.prettyhexrep(identity.hash)) - if not _allow_all and not identity.hash in _allowed_identity_hashes: + if not _allow_all and identity.hash not in _allowed_identity_hashes: log.warning("Identity " + RNS.prettyhexrep(identity.hash) + " not allowed, tearing down link", RNS.LOG_WARNING) link.teardown() @@ -540,7 +543,7 @@ def _listen_request(path, data, request_id, link_id, remote_identity, requested_ return ProcessState.default_response() -async def _spin(until: Callable | None = None, timeout: float | None = None) -> bool: +async def _spin(until: callable = None, timeout: float | None = None) -> bool: if timeout is not None: timeout += time.time() @@ -644,7 +647,8 @@ async def _execute(configdir, identitypath=None, verbosity=0, quietness=0, noid= await _spin( until=lambda: _link.status == RNS.Link.CLOSED or ( - request_receipt.status != RNS.RequestReceipt.FAILED and request_receipt.status != RNS.RequestReceipt.SENT), + request_receipt.status != RNS.RequestReceipt.FAILED and + request_receipt.status != RNS.RequestReceipt.SENT), timeout=timeout ) @@ -667,11 +671,11 @@ async def _execute(configdir, identitypath=None, verbosity=0, quietness=0, noid= if request_receipt.response is not None: try: - running = request_receipt.response[ProcessState.RESPONSE_IDX_RUNNING] or True + running = request_receipt.response[ProcessState.RESPONSE_IDX_RUNNING] or True return_code = request_receipt.response[ProcessState.RESPONSE_IDX_RETCODE] ready_bytes = request_receipt.response[ProcessState.RESPONSE_IDX_RDYBYTE] or 0 - stdout = request_receipt.response[ProcessState.RESPONSE_IDX_STDOUT] - timestamp = request_receipt.response[ProcessState.RESPONSE_IDX_TMSTAMP] + stdout = request_receipt.response[ProcessState.RESPONSE_IDX_STDOUT] + timestamp = request_receipt.response[ProcessState.RESPONSE_IDX_TMSTAMP] # log.debug("data: " + (stdout.decode("utf-8") if stdout is not None else "")) except Exception as e: raise RemoteExecutionError(f"Received invalid response") from e @@ -758,10 +762,9 @@ async def _initiate(configdir: str, identitypath: str, verbosity: int, quietness if return_code is not None: log.debug(f"received return code {return_code}, exiting") - try: + with exception.permit(SystemExit): _link.teardown() - except: - pass + return return_code except RemoteExecutionError as e: print(e.msg) @@ -772,13 +775,15 @@ async def _initiate(configdir: str, identitypath: str, verbosity: int, quietness _T = TypeVar("_T") + def _split_array_at(arr: [_T], at: _T) -> ([_T], [_T]): try: idx = arr.index(at) - return arr[:idx], arr[idx+1:] + return arr[:idx], arr[idx + 1:] except ValueError: return arr, [] + async def main(): global _tr, _finished, _loop import docopt @@ -879,9 +884,8 @@ Options: timeout=args_timeout, ) return return_code if args_mirror else 0 - except: + finally: _tr.restore() - raise else: print("") print(args) @@ -889,17 +893,15 @@ Options: def rnsh_cli(): - return_code = 1 try: return_code = asyncio.run(main()) finally: - try: + with exception.permit(SystemExit): process.tty_unset_reader_callbacks(sys.stdin.fileno()) - except: - pass + _tr.restore() _retry_timer.close() - sys.exit(return_code) + sys.exit(return_code or 255) if __name__ == "__main__": diff --git a/rnsh/rnslogging.py b/rnsh/rnslogging.py index 99288f5..8e6a5f6 100644 --- a/rnsh/rnslogging.py +++ b/rnsh/rnslogging.py @@ -20,17 +20,18 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import asyncio import logging +import sys +import termios from logging import Handler, getLevelName from types import GenericAlias -import os -import tty -from typing import List, Any -import asyncio -import termios -import sys +from typing import Any + import RNS -import json + +import rnsh.exception as exception + class RnsHandler(Handler): """ @@ -77,29 +78,35 @@ class RnsHandler(Handler): __class_getitem__ = classmethod(GenericAlias) + log_format = '%(name)-30s %(message)s [%(threadName)s]' logging.basicConfig( level=logging.DEBUG, # RNS.log will filter it, but some formatting will still be processed before it gets there - #format='%(asctime)s.%(msecs)03d %(levelname)-6s %(threadName)-15s %(name)-15s %(message)s', + # format='%(asctime)s.%(msecs)03d %(levelname)-6s %(threadName)-15s %(name)-15s %(message)s', format=log_format, datefmt='%Y-%m-%d %H:%M:%S', handlers=[RnsHandler()]) -_loop: asyncio.AbstractEventLoop | None = None +_loop: asyncio.AbstractEventLoop = None + + def set_main_loop(loop: asyncio.AbstractEventLoop): global _loop _loop = loop -#hack for temporarily overriding term settings to make debug print right + +# hack for temporarily overriding term settings to make debug print right _rns_log_orig = RNS.log -def _rns_log(msg, level=3, _override_destination = False): + +def _rns_log(msg, level=3, _override_destination=False): if not RNS.compact_log_fmt: msg = (" " * (7 - len(RNS.loglevelname(level)))) + msg + def inner(): - tattr_orig: list[Any] | None = None - try: + tattr_orig: list[Any] = None + with exception.permit(SystemExit): tattr = termios.tcgetattr(sys.stdin.fileno()) tattr_orig = tattr.copy() # tcflag_t c_iflag; /* input modes */ @@ -109,19 +116,16 @@ def _rns_log(msg, level=3, _override_destination = False): # cc_t c_cc[NCCS]; /* special characters */ tattr[1] = tattr[1] | termios.ONLRET | termios.ONLCR | termios.OPOST termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, tattr) - except: - pass _rns_log_orig(msg, level, _override_destination) if tattr_orig is not None: termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, tattr_orig) - try: - if _loop: - _loop.call_soon_threadsafe(inner) - else: - inner() - except: + + if _loop: + _loop.call_soon_threadsafe(inner) + else: inner() -RNS.log = _rns_log \ No newline at end of file + +RNS.log = _rns_log diff --git a/rnsh/testlogging.py b/rnsh/testlogging.py index f2bedbd..38d59c2 100644 --- a/rnsh/testlogging.py +++ b/rnsh/testlogging.py @@ -29,7 +29,8 @@ log_format = '%(levelname)-6s %(name)-40s %(message)s [%(threadName)s]' \ __logging.basicConfig( level=__logging.INFO, - #format='%(asctime)s.%(msecs)03d %(levelname)-6s %(threadName)-15s %(name)-15s %(message)s', + # format='%(asctime)s.%(msecs)03d %(levelname)-6s %(threadName)-15s %(name)-15s %(message)s', format=log_format, datefmt='%Y-%m-%d %H:%M:%S', - handlers=[__logging.StreamHandler()]) \ No newline at end of file + handlers=[__logging.StreamHandler()]) +