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.
--- /dev/null
+/* -*-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 -------------------------------------------------*/