class Strsignal: """An instance of this class emulates the standard (POSIX.1-2008) C library function strsignal(). If possible, it calls the real C library function, using ctypes; if this is not possible, it produces the name of the SIGxxx constant (from the signal module) corresponding to the signal number passed in; if *that* doesn't work either, it returns the string "signal %d" with the %d replaced with the decimal signal number. For testing purposes, functions that implement a single one of the strategies above can be retrieved from the static methods get_ctypes_strsignal, get_reversemap_strsignal, and get_fallback_strsignal. These methods may throw arbitrary exceptions. """ def __init__(self): """Which strategy we will use is lazily determined on the first call.""" self._strsignal = None def __call__(self, signo): """External entry point.""" if self._strsignal is None: self.lazy_init() return self._strsignal(signo) def lazy_init(self): """Pick whichever of our three strategies doesn't throw an exception upon instantiation.""" try: self._strsignal = self.get_ctypes_strsignal() except: try: self._strsignal = self.get_reversemap_strsignal() except: self._strsignal = self.get_fallback_strsignal() @staticmethod def get_ctypes_strsignal(): """Strategy 1: If the C library exposes strsignal(), use it.""" import signal import ctypes import ctypes.util libc = ctypes.CDLL(ctypes.util.find_library("c")) strsignal_proto = ctypes.CFUNCTYPE(ctypes.c_char_p, ctypes.c_int) strsignal_c = strsignal_proto(("strsignal", libc), ((1,),)) NSIG = signal.NSIG def strsignal_ctypes_wrapper(signo): # The behavior of the C library strsignal() is unspecified if # called with an out-of-range argument. Range-check on entry # _and_ NULL-check on exit. if 0 <= signo < NSIG: s = strsignal_c(signo) if s: return s.decode("utf-8") return "Unknown signal "+str(signo) return strsignal_ctypes_wrapper @staticmethod def get_reversemap_strsignal(): """Strategy 2: return the name of the signal constant corresponding to the value passed in.""" import signal signames = [None] * signal.NSIG for constant in dir(signal): if not constant.startswith("SIG"): continue if constant.startswith("SIG_"): continue # obsolete names for signals if constant in ('SIGIOT', 'SIGCLD', 'SIGPOLL'): continue signames[getattr(signal, constant)] = constant for rt in range(signal.SIGRTMIN+1, signal.SIGRTMAX): signames[rt] = "SIGRTMIN+"+str(rt - signal.SIGRTMIN) for gap in range(len(signames)): if signames[gap] is None: signames[gap] = "SIG_"+str(gap) NSIG = signal.NSIG def strsignal_reversemap_wrapper(signo): if 0 <= signo < NSIG: return signames[signo] else: return "Unknown signal "+str(signo) return strsignal_reversemap_wrapper @staticmethod def get_fallback_strsignal(): """Strategy F: just return "signal N" where N is the signal number.""" def strsignal_fallback_wrapper(signo): return "signal "+str(signo) return strsignal_fallback_wrapper strsignal = Strsignal() __all__ = ('strsignal',) if __name__ == '__main__': def main(): import signal import sys import traceback try: strsignal_c = Strsignal.get_ctypes_strsignal() except Exception as e: sys.stdout.write("Ctypes strsignal unavailable: ") sys.stdout.write(traceback.format_exception_only(type(e), e)[0]) strsignal_c = lambda _: "--" try: strsignal_r = Strsignal.get_reversemap_strsignal() except Exception as e: sys.stdout.write("Reverse-map strsignal unavailable: ") sys.stdout.write(traceback.format_exception_only(type(e), e)[0]) strsignal_r = lambda _: "--" try: strsignal_f = Strsignal.get_fallback_strsignal() except Exception as e: sys.stdout.write("Fallback strsignal unavailable: ") sys.stdout.write(traceback.format_exception_only(type(e), e)[0]) strsignal_f = lambda _: "--" try: strsignal(1) strsignal(0) strsignal(-9999) strsignal_a = strsignal except: sys.stdout.write("Automatic strsignal unavailable: ") traceback.print_exc(limit=0, file=sys.stdout) strsignal_a = lambda _: "--" rows = [('N', 'Auto', 'Ctypes', 'Revmap', 'Fallback')] for n in [-signal.NSIG-1, -1] + list(range(signal.NSIG + 2)): rows.append((str(n), strsignal_a(n), strsignal_c(n), strsignal_r(n), strsignal_f(n))) cols = zip(*rows) col_widths = [ max(len(val) for val in col) for col in cols ] form = ' '.join(['{{:<{0}}}'.format(width) for width in col_widths]) form += '\n' for row in rows: sys.stdout.write(form.format(*row)) main()