chiark / gitweb /
src/jobclient.c: Low-level magic for GNU Make's jobserver protocol.
authorMark Wooding <mdw@distorted.org.uk>
Wed, 11 Sep 2019 11:05:39 +0000 (12:05 +0100)
committerMark Wooding <mdw@distorted.org.uk>
Wed, 11 Sep 2019 11:05:57 +0000 (12:05 +0100)
Makefile
src/jobclient.c [new file with mode: 0644]

index 1f34c0ccf7b0f6c5bbc1bec725798b1891d98f40..2b747dd5e7edc57354ff322d9834b8c3e01cda4f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -315,6 +315,39 @@ check-mountpoint    = $(call check,$1,"\`$2' is not a mount point", \
 check-symlink           = $(call check,$1,"\`$2' is not a link to \`$3'", \
        [ -L "$2" ] && [ "$$(readlink "$2")" = "$3" ])
 
+###--------------------------------------------------------------------------
+### Python extensions.
+
+CC                      = gcc
+CFLAGS                  = -O2 -g -Wall
+
+LD                      = $(CC)
+LDFLAGS                         =
+
+c-source                = $(foreach c,$1,src/$c)
+c-object                = $(foreach c,$1,$(STATE)/obj/$(basename $c).o)
+
+PYEXT_PKGFLAGS         := $(shell pkg-config --cflags python2)
+PYEXT_CFLAGS            = -fPIC -fno-strict-aliasing $(PYEXT_PKGFLAGS)
+PYEXT_LDFLAGS           = -shared
+
+PYEXTS                 += jobclient
+jobclient_SOURCES       = jobclient.c
+
+PYEXT_ALLSRC            = $(foreach x,$(PYEXTS),\
+                               $(call c-source,$($x_SOURCES)))
+PYEXT_ALLOBJ            = $(foreach x,$(PYEXTS),\
+                               $(call c-object,$($x_SOURCES)))
+$(PYEXT_ALLOBJ): $(STATE)/obj/%.o: src/%.c
+       $(V_AT)mkdir -p $(dir $@)
+       $(call v_tag,CC)$(CC) -c $(CFLAGS) $(PYEXT_CFLAGS) -o$@ $<
+
+PYMODULES               = $(foreach x,$(PYEXTS),$(STATE)/lib/python/$x.so)
+all:: $(PYMODULES)
+$(PYMODULES): $(STATE)/lib/python/%.so: $$(call c-object,$$($$*_SOURCES))
+       $(V_AT)mkdir -p $(dir $@)
+       $(call v_tag,LD)$(LD) $(LDFLAGS) $(PYEXT_LDFLAGS) -o$@ $^
+
 ###--------------------------------------------------------------------------
 ### Scripts.
 
diff --git a/src/jobclient.c b/src/jobclient.c
new file mode 100644 (file)
index 0000000..38411ce
--- /dev/null
@@ -0,0 +1,140 @@
+/* -*-c-*-
+ *
+ * Job client low-level magic
+ *
+ * (c) 2019 Mark Wooding
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of the distorted.org.uk chroot maintenance tools.
+ *
+ * distorted-chroot is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * distorted-chroot is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with distorted-chroot.  If not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include <assert.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+
+/*----- Static variables --------------------------------------------------*/
+
+static volatile int sigfd = -1;
+
+/*----- Main code ---------------------------------------------------------*/
+
+static void chld(int sig)
+  { int fd = sigfd; if (fd != -1) { close(fd); sigfd = -1; } }
+
+static PyObject *pymeth_jobclient(PyObject *me, PyObject *arg)
+{
+  PyObject *rc = 0;
+  struct sigaction sa, oldsa;
+  sigset_t mask, oldmask;
+  int rstrchld = 0;
+  int fd, sfd;
+  int kid, st;
+  ssize_t n;
+  char ch;
+
+  /* Parse the arguments. */
+  if (!PyArg_ParseTuple(arg, "i", &fd)) goto end;
+
+  /* Prepare to be able to block `SIGCHLD'. */
+  sigemptyset(&mask); sigaddset(&mask, SIGCHLD);
+  if (sigprocmask(SIG_SETMASK, 0, &oldmask)) goto oserr;
+
+  /* Establish the signal handler. */
+  sa.sa_handler = chld;
+  sigemptyset(&sa.sa_mask); sigaddset(&sa.sa_mask, SIGCHLD);
+  sa.sa_flags = SA_NOCLDSTOP;
+  if (sigaction(SIGCHLD, &sa, &oldsa)) goto oserr;
+  rstrchld = 1;
+
+  /* Establish a copy of the file descriptor.  If a signal occurs, we'll
+   * clobber this descriptor: see below.  If we come back here, we'll
+   * probably need to reinstate this, but check just in case: we don't want a
+   * descriptor leak.
+   */
+again:
+  if (sigprocmask(SIG_BLOCK, &mask, 0)) goto oserr;
+  sfd = sigfd;
+  if (sfd == -1) {
+    sfd = dup(fd); if (sfd == -1) goto oserr;
+    sigfd = sfd;
+  }
+  if (sigprocmask(SIG_UNBLOCK, &mask, 0)) goto oserr;
+
+  /* Check for child processes.  If a child exit somehow failed to be
+   * spotted earlier, this is our chance to notice and do something about it.
+   */
+  kid = waitpid(-1, &st, WNOHANG);
+  if (kid == -1 && errno != ECHILD) goto oserr;
+  else if (kid > 0)
+    { rc = Py_BuildValue("(Oii)", Py_None, kid, st); goto end; }
+
+  /* Read from the pipe.  If a child exits between the `waitpid' above and
+   * the `read' here, the signal handler will pop and close the descriptor,
+   * so `read' will report `EBADF'.  If a child exits while we're waiting for
+   * a byte on the descriptor, then `read' will report `EINTR'.  If anything
+   * like this happens, then we go back and try reaping children again.
+   */
+  Py_BEGIN_ALLOW_THREADS
+  n = read(sigfd, &ch, 1);
+  Py_END_ALLOW_THREADS
+  if (n == 1) rc = Py_BuildValue("(cOO)", ch, Py_None, Py_None);
+  else if (!n) rc = Py_BuildValue("(sOO)", "", Py_None, Py_None);
+  else if (n != -1) {
+    PyErr_SetString(PyExc_SystemError, "impossible return from read");
+    goto end;
+  } else if (errno == EINTR || errno == EBADF) goto again;
+  else goto oserr;
+
+  /* We're done. */
+  goto end;
+
+oserr:
+  /* Report a system error. */
+  PyErr_SetFromErrnoWithFilename(PyExc_OSError, 0); goto end;
+
+end:
+  /* Clean up and return. */
+  if (rstrchld) {
+    sigprocmask(SIG_SETMASK, &oldmask, 0);
+    sigaction(SIGCHLD, &oldsa, 0);
+  }
+  sfd = sigfd; if (sfd != -1) close(sfd);
+  return (rc);
+}
+
+static PyMethodDef methods[] = {
+  { "jobclient", pymeth_jobclient, METH_VARARGS,
+    "jobclient(FD) -> (TOKEN, PID, STATUS)" },
+  { 0 }
+};
+
+PyMODINIT_FUNC initjobclient(void)
+  { Py_InitModule("jobclient", methods); }
+
+/*----- That's all, folks -------------------------------------------------*/