Commit | Line | Data |
---|---|---|
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 | ||
44 | static volatile int sigfd = -1; | |
45 | ||
46 | /*----- Main code ---------------------------------------------------------*/ | |
47 | ||
48 | static void chld(int sig) | |
49 | { int fd = sigfd; if (fd != -1) { close(fd); sigfd = -1; } } | |
50 | ||
51 | static 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 | */ | |
82 | again: | |
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 | ||
123 | oserr: | |
124 | /* Report a system error. */ | |
125 | PyErr_SetFromErrnoWithFilename(PyExc_OSError, 0); goto end; | |
126 | ||
127 | end: | |
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 | ||
137 | static PyMethodDef methods[] = { | |
138 | { "jobclient", pymeth_jobclient, METH_VARARGS, | |
139 | "jobclient(FD) -> (TOKEN, PID, STATUS)" }, | |
140 | { 0 } | |
141 | }; | |
142 | ||
143 | PyMODINIT_FUNC initjobclient(void) | |
144 | { Py_InitModule("jobclient", methods); } | |
145 | ||
146 | /*----- That's all, folks -------------------------------------------------*/ |