*
* setup (pre-exec) fd0: null,
* fd[12: fd2-from-outer-caller
- * env fds: orig-fd[01], listener,
- * env fds: call(server-end)(fake)
+ * env fds: listener, call(server-end)(fake)
+ * env fds: orig-fd[01]
* close fd: lockfile
*
* setup (script) runs initialisation parts of the script
#define ACK_BYTE '\n'
+static struct sockaddr_unix socket_sun;
+
+static void propagate_exit_status(int status, const char *what) {
+ int r;
+
+ if (WIFEXITED(status)) {
+ _exit(status);
+ }
+
+ if (WIFSIGNALED(status)) {
+ int sig = WTERMSIG(status);
+ char *signame = strsignal(sig);
+ if (signame == 0) signame = "unknown signal";
+
+ if (! WCOREDUMP(status) &&
+ (sig == SIGINT ||
+ sig == SIGHUP ||
+ sig == SIGPIPE ||
+ sig == SIGKILL)) {
+ struct sigaction sa;
+ FILLZERO(sa);
+ sa.sa_handler = SIG_DFL;
+ r = sigaction(sig, &sa, 0);
+ if (r) diee("failed to reset signal handler while propagating %s",
+ signame);
+
+ sigset_t sset;
+ sigemptyset(&sset);
+ sigaddset(&sset, sig);
+ r = sigprocmask(SA_UNBLOCK, sset, 0);
+ if (r) diee("failed to reset signal block while propagating %s",
+ signame);
+
+ raise(sig);
+ die("unexpectedly kept running after raising (to propagate) %s",
+ signame);
+ }
+
+ die("setup failed due to signal %d %s%s", sig, signame,
+ WCOREDUMP(status) ? " (core dumped)" : "");
+ }
+
+ die("setup failed with weird wait status %d 0x%x", status, status);
+}
+
// Returns: call(client-end) fd, or -1 to mean "is garbage"
// find_socket_path must have been called
static int attempt_connect_existing(void) {
int r;
int fd = -1;
- FILLZERO(sun);
- sun.sun_family = AF_UNIX;
- assert(strlen(socket_path) <= sizeof(sun.sun_path));
- strncpy(sun.sun_path, socket_path, sizeof(sun.sun_path));
-
bool isgarbage = check_garbage();
if (isgarbage) goto x_garbage;
if (fd==-1) diee("socket() for client");
salen_t salen = sizeof(sun);
- r = connect(client, (const struct sockaddr*)sun, salen);
+ r = connect(client, (const struct sockaddr*)&socket_sun, salen);
if (r==-1) {
if (errno==ECONNREFUSED || errno==ENOENT) goto x_garbgae;
diee("connect() %s", socket_path);
return -1;
}
+static void become_setup(int sfd, int fake_pair[2])
+ __attribute__((noreturn))
+{
+ close(fake_pair[0]);
+ int call_fd = fake_pair[1];
+
+ int fd0_save = dup(0); if (fd0_save < 0) diee("dup stdin");
+ int fd1_save = dup(1); if (fd1_save < 0) diee("dup stdin");
+
+ int null_0 = open("/dev/null", O_RDONLY); if (null_0 < 0) diee("open null");
+ if (dup2(null_0, 0)) diee("dup2 /dev/null onto stdin");
+ if (dup2(2, 1) != 1) die("dup2 stderr onto stdout");
+
+ putenv(asprintf("PREFORK_INTERP_FDS=%d,%d,%d,%d",
+ sfd, call_fd, fd0_save, fd1_save));
+ execv(
+}
+
static int connect_or_spawn(void) {
int fd = connect_existing();
if (fd >= 0) return fd;
- let acquire_lock();
- fd = connect_existing();
+ int lockfd = acquire_lock();
+ fd = connect_existing();
+ if (fd >= 0) { close(lockfd); return fd; }
+
+ // We must start a fresh one, and we hold the lock
+
+ r = unlink(socketpath);
+ if (r<0) diee("failed to remove stale socket %s", socketpath);
+
+ int fake_pair[2];
+ r = socketpair(AF_UNIX, SOCK_STREAM, 0, fake_pair);
+ if (r<0) diee("socketpair() for fake initial connection");
+
+ int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sfd<0) diee("socket() for new listener");
+
+ salen_t salen = sizeof(sun);
+ r= bind(sfd, (const struct sockaddr*)&socket_sun, saledn);
+ if (r<0) diee("bind() on new listener");
+
+ // We never want callers to get ECONNREFUSED!.
+ // There is a race here: from my RTFM they may get ECONNREFUSED
+ // if they tr between our bind() and listen(). But if they do, they'll
+ // acquire the lock (serialising with us) and retry, and then it will work.
+ r = listen(sfd, INT_MAX);
+ if (r<0) diee("listen() for new listener");
+
+ pid_t setup_pid = fork();
+ if (setup_pid == (pid_t)-1) diee("fork for spawn setup");
+ if (!setup_pid) become_setup(sfd, fake_pair);
+ close(fake_pair[1]);
+ close(sfd);
+
+ int status;
+ pid_t got = waitpid(setup_pid, &status, 0);
+ if (got == (pid_t)-1) diee("waitpid setup [%ld]", (long)setup_pid);
+ if (got != setup_pid) diee("waitpid setup [%ld] gave [%ld]!",
+ (long)setup_pid, (long)got);
+ if (status != 0) propagate_exit_status(status);
+
+ close(lockfd);
+ return fake_pair[0];
+}
int main(int argc, const char *const *argv) {
script = process_opts(argc, argv);
find_socket_path();
- int fd = connect_or_spawn();
+ FILLZERO(sun);
+ sun.sun_family = AF_UNIX;
+ assert(strlen(socket_path) <= sizeof(sun.sun_path));
+ strncpy(sun.sun_path, socket_path, sizeof(sun.sun_path));
+
+ int call_fd = connect_or_spawn();
}