From: Mark Wooding Date: Wed, 11 Sep 2019 11:05:39 +0000 (+0100) Subject: src/jobclient.c: Low-level magic for GNU Make's jobserver protocol. X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/distorted-chroot/commitdiff_plain/af5079e4ca8ca835024f0f5fc333d3cb529a6c1c src/jobclient.c: Low-level magic for GNU Make's jobserver protocol. --- diff --git a/Makefile b/Makefile index 1f34c0c..2b747dd 100644 --- 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 index 0000000..38411ce --- /dev/null +++ b/src/jobclient.c @@ -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 +#include +#include + +#include +#include +#include + +#define PY_SSIZE_T_CLEAN +#include + +/*----- 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 -------------------------------------------------*/