Welcome, guest | Sign In | My Account | Store | Cart

This is an emulation of the standard (POSIX.1-2008) C library function strsignal(), which should be in the signal module, but isn't.

If possible, it uses ctypes to call the C library function; otherwise, it tries to build a reverse mapping of the SIG* constants in the signal module; if that doesn't work either, it just produces "signal %d" where %d is the decimal signal number.

If invoked as a script, will test all of the strategies and print the results.

Tested in 2.7 and 3.4; should work with 2.6 and 3.3 as well.

Python, 161 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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()
Created by Zack Weinberg on Sun, 29 Jun 2014 (MIT)
Python recipes (4591)
Zack Weinberg's recipes (3)

Required Modules

  • (none specified)

Other Information and Tasks