3 * Job client low-level magic
5 * (c) 2019 Mark Wooding
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the distorted.org.uk chroot maintenance tools.
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.
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.
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,
28 /*----- Header files ------------------------------------------------------*/
34 #include <sys/types.h>
36 #include <sys/select.h>
39 #define PY_SSIZE_T_CLEAN
42 /*----- Static variables --------------------------------------------------*/
44 static volatile int sigfd
= -1;
46 /*----- Main code ---------------------------------------------------------*/
48 static void chld(int sig
)
49 { int fd
= sigfd
; if (fd
!= -1) { close(fd
); sigfd
= -1; } }
51 static PyObject
*pymeth_jobclient(PyObject
*me
, PyObject
*arg
)
54 struct sigaction sa
, oldsa
;
55 sigset_t mask
, oldmask
;
63 /* Parse the arguments. */
64 if (!PyArg_ParseTuple(arg
, "i", &fd
)) goto end
;
66 /* Prepare to be able to block `SIGCHLD'. */
67 sigemptyset(&mask
); sigaddset(&mask
, SIGCHLD
);
68 if (sigprocmask(SIG_SETMASK
, 0, &oldmask
)) goto oserr
;
70 /* Establish the signal handler. */
72 sigemptyset(&sa
.sa_mask
); sigaddset(&sa
.sa_mask
, SIGCHLD
);
73 sa
.sa_flags
= SA_NOCLDSTOP
;
74 if (sigaction(SIGCHLD
, &sa
, &oldsa
)) goto oserr
;
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
83 if (sigprocmask(SIG_BLOCK
, &mask
, 0)) goto oserr
;
86 sfd
= dup(fd
); if (sfd
== -1) goto oserr
;
89 if (sigprocmask(SIG_UNBLOCK
, &mask
, 0)) goto oserr
;
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.
94 kid
= waitpid(-1, &st
, WNOHANG
);
95 if (kid
== -1 && errno
!= ECHILD
) goto oserr
;
97 { rc
= Py_BuildValue("(Oii)", Py_None
, kid
, st
); goto end
; }
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.
105 Py_BEGIN_ALLOW_THREADS
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;
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
);
115 PyErr_SetString(PyExc_SystemError
, "impossible return from read");
117 } else if (errno
== EINTR
|| errno
== EBADF
) goto again
;
124 /* Report a system error. */
125 PyErr_SetFromErrnoWithFilename(PyExc_OSError
, 0); goto end
;
128 /* Clean up and return. */
130 sigprocmask(SIG_SETMASK
, &oldmask
, 0);
131 sigaction(SIGCHLD
, &oldsa
, 0);
133 sfd
= sigfd
; if (sfd
!= -1) { close(sfd
); sigfd
= -1; }
137 static PyMethodDef methods
[] = {
138 { "jobclient", pymeth_jobclient
, METH_VARARGS
,
139 "jobclient(FD) -> (TOKEN, PID, STATUS)" },
143 PyMODINIT_FUNC
initjobclient(void)
144 { Py_InitModule("jobclient", methods
); }
146 /*----- That's all, folks -------------------------------------------------*/