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> | |
36 | #include <unistd.h> | |
37 | ||
38 | #define PY_SSIZE_T_CLEAN | |
39 | #include <Python.h> | |
40 | ||
41 | /*----- Static variables --------------------------------------------------*/ | |
42 | ||
43 | static volatile int sigfd = -1; | |
44 | ||
45 | /*----- Main code ---------------------------------------------------------*/ | |
46 | ||
47 | static void chld(int sig) | |
48 | { int fd = sigfd; if (fd != -1) { close(fd); sigfd = -1; } } | |
49 | ||
50 | static PyObject *pymeth_jobclient(PyObject *me, PyObject *arg) | |
51 | { | |
52 | PyObject *rc = 0; | |
53 | struct sigaction sa, oldsa; | |
54 | sigset_t mask, oldmask; | |
55 | int rstrchld = 0; | |
56 | int fd, sfd; | |
57 | int kid, st; | |
58 | ssize_t n; | |
59 | char ch; | |
60 | ||
61 | /* Parse the arguments. */ | |
62 | if (!PyArg_ParseTuple(arg, "i", &fd)) goto end; | |
63 | ||
64 | /* Prepare to be able to block `SIGCHLD'. */ | |
65 | sigemptyset(&mask); sigaddset(&mask, SIGCHLD); | |
66 | if (sigprocmask(SIG_SETMASK, 0, &oldmask)) goto oserr; | |
67 | ||
68 | /* Establish the signal handler. */ | |
69 | sa.sa_handler = chld; | |
70 | sigemptyset(&sa.sa_mask); sigaddset(&sa.sa_mask, SIGCHLD); | |
71 | sa.sa_flags = SA_NOCLDSTOP; | |
72 | if (sigaction(SIGCHLD, &sa, &oldsa)) goto oserr; | |
73 | rstrchld = 1; | |
74 | ||
75 | /* Establish a copy of the file descriptor. If a signal occurs, we'll | |
76 | * clobber this descriptor: see below. If we come back here, we'll | |
77 | * probably need to reinstate this, but check just in case: we don't want a | |
78 | * descriptor leak. | |
79 | */ | |
80 | again: | |
81 | if (sigprocmask(SIG_BLOCK, &mask, 0)) goto oserr; | |
82 | sfd = sigfd; | |
83 | if (sfd == -1) { | |
84 | sfd = dup(fd); if (sfd == -1) goto oserr; | |
85 | sigfd = sfd; | |
86 | } | |
87 | if (sigprocmask(SIG_UNBLOCK, &mask, 0)) goto oserr; | |
88 | ||
89 | /* Check for child processes. If a child exit somehow failed to be | |
90 | * spotted earlier, this is our chance to notice and do something about it. | |
91 | */ | |
92 | kid = waitpid(-1, &st, WNOHANG); | |
93 | if (kid == -1 && errno != ECHILD) goto oserr; | |
94 | else if (kid > 0) | |
95 | { rc = Py_BuildValue("(Oii)", Py_None, kid, st); goto end; } | |
96 | ||
97 | /* Read from the pipe. If a child exits between the `waitpid' above and | |
98 | * the `read' here, the signal handler will pop and close the descriptor, | |
99 | * so `read' will report `EBADF'. If a child exits while we're waiting for | |
100 | * a byte on the descriptor, then `read' will report `EINTR'. If anything | |
101 | * like this happens, then we go back and try reaping children again. | |
102 | */ | |
103 | Py_BEGIN_ALLOW_THREADS | |
104 | n = read(sigfd, &ch, 1); | |
105 | Py_END_ALLOW_THREADS | |
106 | if (n == 1) rc = Py_BuildValue("(cOO)", ch, Py_None, Py_None); | |
107 | else if (!n) rc = Py_BuildValue("(sOO)", "", Py_None, Py_None); | |
108 | else if (n != -1) { | |
109 | PyErr_SetString(PyExc_SystemError, "impossible return from read"); | |
110 | goto end; | |
111 | } else if (errno == EINTR || errno == EBADF) goto again; | |
112 | else goto oserr; | |
113 | ||
114 | /* We're done. */ | |
115 | goto end; | |
116 | ||
117 | oserr: | |
118 | /* Report a system error. */ | |
119 | PyErr_SetFromErrnoWithFilename(PyExc_OSError, 0); goto end; | |
120 | ||
121 | end: | |
122 | /* Clean up and return. */ | |
123 | if (rstrchld) { | |
124 | sigprocmask(SIG_SETMASK, &oldmask, 0); | |
125 | sigaction(SIGCHLD, &oldsa, 0); | |
126 | } | |
127 | sfd = sigfd; if (sfd != -1) { close(sfd); sigfd = -1; } | |
128 | return (rc); | |
129 | } | |
130 | ||
131 | static PyMethodDef methods[] = { | |
132 | { "jobclient", pymeth_jobclient, METH_VARARGS, | |
133 | "jobclient(FD) -> (TOKEN, PID, STATUS)" }, | |
134 | { 0 } | |
135 | }; | |
136 | ||
137 | PyMODINIT_FUNC initjobclient(void) | |
138 | { Py_InitModule("jobclient", methods); } | |
139 | ||
140 | /*----- That's all, folks -------------------------------------------------*/ |