chiark / gitweb /
src/jobclient.c: Cope if the jobserver pipe is set nonblocking.
[distorted-chroot] / src / jobclient.c
CommitLineData
a4c5d71a
MW
1/* -*-c-*-
2 *
3 * Job client low-level magic
4 *
5 * (c) 2019 Mark Wooding
6 */
7
8/*----- Licensing notice --------------------------------------------------*
9 *
10 * This file is part of the distorted.org.uk chroot maintenance tools.
11 *
12 * distorted-chroot is free software: you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License as
14 * published by the Free Software Foundation; either version 2 of the
15 * License, or (at your option) any later version.
16 *
17 * distorted-chroot is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with distorted-chroot. If not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
25 * USA.
26 */
27
28/*----- Header files ------------------------------------------------------*/
29
30#include <assert.h>
31#include <errno.h>
32#include <signal.h>
33
34#include <sys/types.h>
35#include <sys/wait.h>
4ff3ebbb 36#include <sys/select.h>
a4c5d71a
MW
37#include <unistd.h>
38
39#define PY_SSIZE_T_CLEAN
40#include <Python.h>
41
42/*----- Static variables --------------------------------------------------*/
43
44static volatile int sigfd = -1;
45
46/*----- Main code ---------------------------------------------------------*/
47
48static void chld(int sig)
49 { int fd = sigfd; if (fd != -1) { close(fd); sigfd = -1; } }
50
51static PyObject *pymeth_jobclient(PyObject *me, PyObject *arg)
52{
53 PyObject *rc = 0;
54 struct sigaction sa, oldsa;
55 sigset_t mask, oldmask;
4ff3ebbb 56 fd_set infd;
a4c5d71a
MW
57 int rstrchld = 0;
58 int fd, sfd;
59 int kid, st;
60 ssize_t n;
61 char ch;
62
63 /* Parse the arguments. */
64 if (!PyArg_ParseTuple(arg, "i", &fd)) goto end;
65
66 /* Prepare to be able to block `SIGCHLD'. */
67 sigemptyset(&mask); sigaddset(&mask, SIGCHLD);
68 if (sigprocmask(SIG_SETMASK, 0, &oldmask)) goto oserr;
69
70 /* Establish the signal handler. */
71 sa.sa_handler = chld;
72 sigemptyset(&sa.sa_mask); sigaddset(&sa.sa_mask, SIGCHLD);
73 sa.sa_flags = SA_NOCLDSTOP;
74 if (sigaction(SIGCHLD, &sa, &oldsa)) goto oserr;
75 rstrchld = 1;
76
77 /* Establish a copy of the file descriptor. If a signal occurs, we'll
78 * clobber this descriptor: see below. If we come back here, we'll
79 * probably need to reinstate this, but check just in case: we don't want a
80 * descriptor leak.
81 */
82again:
83 if (sigprocmask(SIG_BLOCK, &mask, 0)) goto oserr;
84 sfd = sigfd;
85 if (sfd == -1) {
86 sfd = dup(fd); if (sfd == -1) goto oserr;
87 sigfd = sfd;
88 }
89 if (sigprocmask(SIG_UNBLOCK, &mask, 0)) goto oserr;
90
91 /* Check for child processes. If a child exit somehow failed to be
92 * spotted earlier, this is our chance to notice and do something about it.
93 */
94 kid = waitpid(-1, &st, WNOHANG);
95 if (kid == -1 && errno != ECHILD) goto oserr;
96 else if (kid > 0)
97 { rc = Py_BuildValue("(Oii)", Py_None, kid, st); goto end; }
98
99 /* Read from the pipe. If a child exits between the `waitpid' above and
100 * the `read' here, the signal handler will pop and close the descriptor,
101 * so `read' will report `EBADF'. If a child exits while we're waiting for
102 * a byte on the descriptor, then `read' will report `EINTR'. If anything
103 * like this happens, then we go back and try reaping children again.
104 */
105 Py_BEGIN_ALLOW_THREADS
4ff3ebbb
MW
106 for (;;) {
107 n = read(sigfd, &ch, 1); if (n >= 0 || errno != EAGAIN) break;
108 FD_ZERO(&infd); FD_SET(sigfd, &infd);
109 n = select(sigfd + 1, &infd, 0, 0, 0); if (n < 0) break;
110 }
a4c5d71a
MW
111 Py_END_ALLOW_THREADS
112 if (n == 1) rc = Py_BuildValue("(cOO)", ch, Py_None, Py_None);
113 else if (!n) rc = Py_BuildValue("(sOO)", "", Py_None, Py_None);
114 else if (n != -1) {
115 PyErr_SetString(PyExc_SystemError, "impossible return from read");
116 goto end;
117 } else if (errno == EINTR || errno == EBADF) goto again;
118 else goto oserr;
119
120 /* We're done. */
121 goto end;
122
123oserr:
124 /* Report a system error. */
125 PyErr_SetFromErrnoWithFilename(PyExc_OSError, 0); goto end;
126
127end:
128 /* Clean up and return. */
129 if (rstrchld) {
130 sigprocmask(SIG_SETMASK, &oldmask, 0);
131 sigaction(SIGCHLD, &oldsa, 0);
132 }
133 sfd = sigfd; if (sfd != -1) { close(sfd); sigfd = -1; }
134 return (rc);
135}
136
137static PyMethodDef methods[] = {
138 { "jobclient", pymeth_jobclient, METH_VARARGS,
139 "jobclient(FD) -> (TOKEN, PID, STATUS)" },
140 { 0 }
141};
142
143PyMODINIT_FUNC initjobclient(void)
144 { Py_InitModule("jobclient", methods); }
145
146/*----- That's all, folks -------------------------------------------------*/