Welcome, guest | Sign In | My Account | Store | Cart
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()

History