Recently I wanted to call some C code from Python to check who was sending a particular signal. Apparently this functionality is only available as of Python 3 so I decided to write a custom module.

Doing so is relatively easy but all the documentation I found only was usingdistutils to build the module and skipped over how to find the newly builtmodule. This post is mostly a write up of what I found out.

Compiling the Module

Compiling the Python module is easy. You just need to correct flags to pass tothe compiler. This information is easily accessible trough the python-configutility.

Additionally you want to make a shared library. On macOS that means adding-dynamiclib to the invocation.

clang `python-config --cflags` `python-config --ldflags` -dynamiclib mymodule.c -o mymodule.so

Make sure that the python-config in your path matches the python in yourpath. For example on macOS, if you have installed python using Homebrew, youmight have a different Python in /usr/bin and /usr/local/bin.

Loading the Module

The most straightforward approach is just having the shared library living inthe current directory. All you need is import mymodule.

Python 2.7.10
Type "help", "copyright", "credits" or "license" for more information.
>>> import mymodule

Alternatively you can add the path where the modules lives to theDYLD_LIBRARY_PATH or LD_LIBRARY_PATH for macOS or Linux respectively, ormore specifically the PYTHONPATH.

export DYLD_LIBRARY_PATH='/path/to/module':$DYLD_LIBRARY_PATH

Please note that the extension seems to matter. Giving the shared library theusual .dylib extension on macOS did not work for me.

Python Module

Below is a listing with the code for the Python module.

#include <Python.h>
#include <libproc.h>
#include <signal.h>

static void sighandler_handle(int signum, siginfo_t *siginfo, void *context) {
  int pid = getpid();
  int pgrp = getpgrp();
  int si_pid = (int)siginfo->si_pid;
  char si_path[PROC_PIDPATHINFO_MAXSIZE];

  // Print some info about the receiving and sending process.
  fprintf(stderr, "Signal received:\n");
  fprintf(stderr, "\tsignal: %d\n", signum);
  fprintf(stderr, "\tpid:    %d\n", pid);
  fprintf(stderr, "\tpgrp:   %d\n", pgrp);
  fprintf(stderr, "\tfrom:   %d\n", si_pid);

  // Try obtaining the process name for the sending pid.
  int ret = proc_pidpath(si_pid, si_path, sizeof(si_path));
  if (ret <= 0) {
    fprintf(stderr, "\tpath:   %s\n", strerror(errno));
  } else {
    fprintf(stderr, "\tpath:   %s\n", si_path);
  }

  exit(1);
}

static void sighandler_set(int signal) {
  struct sigaction sa;
  memset(&sa, '\0', sizeof(sa));
  sa.sa_sigaction = &sighandler_handle;
  sa.sa_flags = SA_SIGINFO;
  sigaction(signal, &sa, NULL);
}

static PyObject *sighandler_register(PyObject *self, PyObject *args) {
  int signal;
  if (!PyArg_ParseTuple(args, "i", &signal))
    return NULL;
  sighandler_set(signal);
  Py_RETURN_NONE;
}

static PyMethodDef sighandler_methods[] = {
    {"register", sighandler_register, METH_VARARGS,
     "Register signal handler by signal number"},
    {NULL, NULL, 0, NULL},
};

PyMODINIT_FUNC initsighandler(void) {
  PyObject *m = Py_InitModule("sighandler", sighandler_methods);
  if (m == NULL)
    return;
}