diff options
-rw-r--r-- | ChangeLog.txt | 3 | ||||
-rw-r--r-- | enums/exit_status.in | 16 | ||||
-rw-r--r-- | pseudo.c | 87 | ||||
-rw-r--r-- | pseudo_client.c | 9 | ||||
-rw-r--r-- | pseudo_server.c | 236 |
5 files changed, 249 insertions, 102 deletions
diff --git a/ChangeLog.txt b/ChangeLog.txt index cc2f379..6031496 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,3 +1,6 @@ +2016-03-01: + * (seebs) server launch reworking + 2016-02-24: * (seebs) event logger cleanup, improved diagnostics for client spawning, server exit cleanup. diff --git a/enums/exit_status.in b/enums/exit_status.in new file mode 100644 index 0000000..e02b1bc --- /dev/null +++ b/enums/exit_status.in @@ -0,0 +1,16 @@ +exit_status: PSEUDO_EXIT; char * message = "exit status unknown" +# 0 indicates success. The others indicate where in the startup process +# something went wrong, for any point at which we'd exit. +general, "unspecified error" +fork_failed, "fork failed" +lock_path, "path allocation failure for lock file" +lock_held, "lock already held by another process" +lock_failed, "could not create/lock lockfile" +timeout, "child process timed out" +waitpid, "waitpid() for child process failed unexpectedly" +socket_create, "couldn't create socket" +socket_fd, "couldn't move socket to safe file descriptor" +socket_path, "path allocation failure for server socket" +socket_unlink, "couldn't unlink existing server socket" +socket_bind, "couldn't bind server socket" +socket_listen, "couldn't listen on server socket" @@ -19,7 +19,7 @@ */ #include <stdlib.h> #include <ctype.h> -#include <stdlib.h> +#include <signal.h> #include <string.h> #include <stdio.h> #include <errno.h> @@ -85,44 +85,33 @@ usage(int status) { exit(status); } -/* helper function to make a directory, just like mkdir -p. - * Can't use system() because the child shell would end up trying - * to do the same thing... - */ -static void -mkdir_p(char *path) { - size_t len = strlen(path); - size_t i; - - for (i = 1; i < len; ++i) { - /* try to create all the directories in path, ignoring - * failures - */ - if (path[i] == '/') { - path[i] = '\0'; - (void) mkdir(path, 0755); - path[i] = '/'; - } - } - (void) mkdir(path, 0755); -} /* main server process */ int main(int argc, char *argv[]) { int o; char *s; - int lockfd, newfd; char *ld_env = getenv(PRELINK_LIBRARIES); int rc = 0; char opts[pseudo_path_max()], *optptr = opts; - char *lockname; - char *lockpath; + sigset_t blocked, saved; opts[0] = '\0'; pseudo_init_util(); + /* The pseudo client will have blocked these, and sigprocmask + * is inherited, but we want them to work. + */ + sigemptyset(&blocked); + sigaddset(&blocked, SIGALRM); /* every-N-seconds tasks */ + sigaddset(&blocked, SIGCHLD); /* reaping child processes */ + sigaddset(&blocked, SIGHUP); /* idiomatically, reloading config */ + sigaddset(&blocked, SIGTERM); /* shutdown/teardown operations */ + sigaddset(&blocked, SIGUSR1); /* reopening log files, sometimes */ + sigaddset(&blocked, SIGUSR2); /* who knows what people do */ + sigprocmask(SIG_UNBLOCK, &blocked, &saved); + if (ld_env && strstr(ld_env, "libpseudo")) { pseudo_debug(PDBGF_SERVER, "can't run daemon with libpseudo in %s\n", PRELINK_LIBRARIES); s = pseudo_get_value("PSEUDO_UNLOAD"); @@ -439,54 +428,6 @@ main(int argc, char *argv[]) { exit(EXIT_FAILURE); } } - /* if we got here, we are not running a command, and we are not in - * a pseudo environment. - */ - pseudo_new_pid(); - - pseudo_debug(PDBGF_SERVER, "opening lock.\n"); - lockpath = pseudo_localstatedir_path(NULL); - if (!lockpath) { - pseudo_diag("Couldn't allocate a file path.\n"); - exit(EXIT_FAILURE); - } - mkdir_p(lockpath); - lockname = pseudo_localstatedir_path(PSEUDO_LOCKFILE); - if (!lockname) { - pseudo_diag("Couldn't allocate a file path.\n"); - exit(EXIT_FAILURE); - } - lockfd = open(lockname, O_RDWR | O_CREAT, 0644); - if (lockfd < 0) { - pseudo_diag("Can't open or create lockfile %s: %s\n", - lockname, strerror(errno)); - exit(EXIT_FAILURE); - } - free(lockname); - - if (lockfd <= 2) { - newfd = fcntl(lockfd, F_DUPFD, 3); - if (newfd < 0) { - pseudo_diag("Can't move lockfile to safe descriptor: %s\n", - strerror(errno)); - } else { - close(lockfd); - lockfd = newfd; - } - } - - pseudo_debug(PDBGF_SERVER, "acquiring lock.\n"); - if (flock(lockfd, LOCK_EX | LOCK_NB) < 0) { - if (errno == EACCES || errno == EAGAIN) { - pseudo_debug(PDBGF_SERVER, "Existing server has lock. Exiting.\n"); - } else { - pseudo_diag("pseudo: Error obtaining lock: %s\n", strerror(errno)); - } - exit(1); - } else { - pseudo_debug(PDBGF_SERVER, "Acquired lock.\n"); - } - pseudo_debug(PDBGF_SERVER, "serving (%s)\n", opt_d ? "daemon" : "foreground"); return pseudo_server_start(opt_d); } diff --git a/pseudo_client.c b/pseudo_client.c index ee831d1..4d73598 100644 --- a/pseudo_client.c +++ b/pseudo_client.c @@ -938,8 +938,15 @@ client_spawn_server(void) { int status; FILE *fp; char * pseudo_pidfile; + char *old_value = pseudo_get_value("PSEUDO_UNLOAD"); + /* drop pseudo immediately so the fork/exec are lower-load. */ + pseudo_set_value("PSEUDO_UNLOAD", "YES"); if ((server_pid = fork()) != 0) { + /* restore PSEUDO_UNLOAD in caller */ + pseudo_set_value("PSEUDO_UNLOAD", old_value); + free(old_value); + if (server_pid == -1) { pseudo_diag("couldn't fork server: %s\n", strerror(errno)); return 1; @@ -1767,7 +1774,7 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path break; case OP_DUP: /* just copy the path over */ - pseudo_debug(PDBGF_CLIENT, "dup: fd_path(%d) = %p [%s], dup to %d\n", + pseudo_debug(PDBGF_CLIENT | PDBGF_VERBOSE, "dup: fd_path(%d) = %p [%s], dup to %d\n", fd, fd_path(fd), fd_path(fd) ? fd_path(fd) : "<nil>", dirfd); pseudo_client_path(dirfd, fd_path(fd)); diff --git a/pseudo_server.c b/pseudo_server.c index 34d1d54..f6806f3 100644 --- a/pseudo_server.c +++ b/pseudo_server.c @@ -82,6 +82,29 @@ static struct timeval message_time = { .tv_sec = 0 }; static void pseudo_server_loop(void); +/* helper function to make a directory, just like mkdir -p. + * Can't use system() because the child shell would end up trying + * to do the same thing... + */ +static void +mkdir_p(char *path) { + size_t len = strlen(path); + size_t i; + + for (i = 1; i < len; ++i) { + /* try to create all the directories in path, ignoring + * failures + */ + if (path[i] == '/') { + path[i] = '\0'; + (void) mkdir(path, 0755); + path[i] = '/'; + } + } + (void) mkdir(path, 0755); +} + + static int pseudo_server_write_pid(pid_t pid) { char *pseudo_path; @@ -104,11 +127,175 @@ pseudo_server_write_pid(pid_t pid) { return 0; } +static sig_atomic_t got_sigusr1 = 0; +static sig_atomic_t got_sigalrm = 0; + +static void +handle_sigusr1(int sig) { + (void) sig; + pseudo_diag("sigusr1\n"); + got_sigusr1 = 1; +} + +static void +handle_sigalrm(int sig) { + (void) sig; + pseudo_diag("sigalrm\n"); + got_sigalrm = 1; +} + +#define PSEUDO_CHILD_PROCESS_TIMEOUT 2 int pseudo_server_start(int daemonize) { struct sockaddr_un sun = { .sun_family = AF_UNIX, .sun_path = PSEUDO_SOCKET }; char *pseudo_path; - int rc, newfd; + int newfd, lockfd; + int rc, save_errno; + char *lockname; + char *lockpath; + struct flock lock_data; + + /* parent process will wait for child process, or until it gets + * SIGUSR1, or until too much time has passed. */ + pseudo_diag("server start: daemonize %d.\n", daemonize); + if (daemonize) { + int child; + child = fork(); + if (child == -1) { + pseudo_diag("Couldn't fork child process: %s\n", + strerror(errno)); + exit(PSEUDO_EXIT_FORK_FAILED); + } + if (child) { + int status; + int rc; + int save_errno; + pseudo_diag("parent process, pid %d, doing setup.\n", getpid()); + + got_sigusr1 = 0; + signal(SIGUSR1, handle_sigusr1); + signal(SIGALRM, handle_sigalrm); + alarm(PSEUDO_CHILD_PROCESS_TIMEOUT); + int tries = 0; + do { + rc = waitpid(child, &status, WNOHANG); + save_errno = errno; + if (rc != child && !got_sigalrm && !got_sigusr1) { + struct timespec delay = { .tv_sec = 0, .tv_nsec = 100000000 }; + nanosleep(&delay, NULL); + ++tries; + } + + } while (!got_sigalrm && !got_sigusr1 && rc != child); + alarm(0); + pseudo_diag("pid waited: %d/%d [%d tries], status %d\n", rc, save_errno, tries, status); + pseudo_diag("usr1: %d alrm: %d\n", got_sigusr1, got_sigalrm); + if (got_sigusr1) { + pseudo_debug(PDBGF_SERVER, "server says it's ready.\n"); + exit(0); + } + if (save_errno == EINTR) { + pseudo_diag("Child process timeout after %d seconds.\n", + PSEUDO_CHILD_PROCESS_TIMEOUT); + exit(PSEUDO_EXIT_TIMEOUT); + } + if (rc == -1) { + pseudo_diag("Failure in waitpid(): %s\n", + strerror(save_errno)); + exit(PSEUDO_EXIT_WAITPID); + } + if (WIFSIGNALED(status)) { + status = WTERMSIG(status); + pseudo_diag("Child process exited from signal %d.\n", + status); + kill(getpid(), status); + /* can't use +128 because that's not valid */ + exit(status + 64); + } + if (WIFEXITED(status)) { + status = WEXITSTATUS(status); + pseudo_diag("Child process exit status %d: %s\n", + status, + pseudo_exit_status_name(status)); + exit(status); + } + pseudo_diag("Unknown exit status %d.\n", status); + exit(1); + } else { + /* detach from parent session */ + setsid(); + fclose(stdin); + fclose(stdout); + pseudo_debug_logfile(PSEUDO_LOGFILE, 2); + /* and then just execute the server code normally. */ + /* Any logging will presumably go to logfile, but + * exit status will make it back to the parent for + * reporting. */ + } + } + + pseudo_diag("pid %d [parent %d], doing new pid setup and server start\n", getpid(), getppid()); + pseudo_new_pid(); + + pseudo_debug(PDBGF_SERVER, "opening lock.\n"); + lockpath = pseudo_localstatedir_path(NULL); + if (!lockpath) { + pseudo_diag("Couldn't allocate a file path.\n"); + exit(PSEUDO_EXIT_LOCK_PATH); + } + mkdir_p(lockpath); + lockname = pseudo_localstatedir_path(PSEUDO_LOCKFILE); + if (!lockname) { + pseudo_diag("Couldn't allocate a file path.\n"); + exit(PSEUDO_EXIT_LOCK_PATH); + } + lockfd = open(lockname, O_RDWR | O_CREAT, 0644); + if (lockfd < 0) { + pseudo_diag("Can't open or create lockfile %s: %s\n", + lockname, strerror(errno)); + exit(PSEUDO_EXIT_LOCK_FAILED); + } + free(lockname); + + /* the lock shuffle has to happen before an fcntl lock, which + * is automatically dropped if *any* file descriptor on the file + * is closed... + */ + if (lockfd <= 2) { + newfd = fcntl(lockfd, F_DUPFD, 3); + if (newfd < 0) { + pseudo_diag("Can't move lockfile to safe descriptor, leaving it on %d: %s\n", + lockfd, strerror(errno)); + } else { + close(lockfd); + lockfd = newfd; + } + } + + pseudo_debug(PDBGF_SERVER, "acquiring lock.\n"); + + memset(&lock_data, 0, sizeof(lock_data)); + lock_data.l_type = F_WRLCK; + lock_data.l_whence = SEEK_SET; + lock_data.l_start = 0; + lock_data.l_len = 0; + + rc = fcntl(lockfd, F_SETLK, &lock_data); + if (rc < 0) { + save_errno = errno; + if (save_errno == EACCES || save_errno == EAGAIN) { + rc = fcntl(lockfd, F_GETLK, &lock_data); + if (rc == 0 && lock_data.l_type != F_UNLCK) { + pseudo_diag("lock already held by existing pid %d.\n", + lock_data.l_pid); + } + } + pseudo_diag("Couldn't obtain lock: %s.\n", strerror(save_errno)); + exit(PSEUDO_EXIT_LOCK_HELD); + + } else { + pseudo_debug(PDBGF_SERVER, "Acquired lock.\n"); + } #if PSEUDO_PORT_DARWIN sun.sun_len = strlen(PSEUDO_SOCKET) + 1; @@ -117,7 +304,7 @@ pseudo_server_start(int daemonize) { listen_fd = socket(PF_UNIX, SOCK_STREAM, 0); if (listen_fd < 0) { pseudo_diag("couldn't create listening socket: %s\n", strerror(errno)); - return 1; + exit(PSEUDO_EXIT_SOCKET_CREATE); } if (listen_fd <= 2) { @@ -125,7 +312,7 @@ pseudo_server_start(int daemonize) { if (newfd < 0) { pseudo_diag("couldn't dup listening socket: %s\n", strerror(errno)); close(listen_fd); - return 1; + exit(PSEUDO_EXIT_SOCKET_FD); } else { close(listen_fd); listen_fd = newfd; @@ -137,46 +324,39 @@ pseudo_server_start(int daemonize) { if (!pseudo_path || chdir(pseudo_path) == -1) { pseudo_diag("can't get to '%s': %s\n", pseudo_path, strerror(errno)); - return 1; + exit(PSEUDO_EXIT_SOCKET_PATH); } free(pseudo_path); /* remove existing socket -- if it exists */ - unlink(sun.sun_path); + rc = unlink(sun.sun_path); + if (rc == -1 && errno != ENOENT) { + pseudo_diag("Can't unlink existing socket: %s.\n", + strerror(errno)); + exit(PSEUDO_EXIT_SOCKET_UNLINK); + } if (bind(listen_fd, (struct sockaddr *) &sun, sizeof(sun)) == -1) { pseudo_diag("couldn't bind listening socket: %s\n", strerror(errno)); - return 1; + exit(PSEUDO_EXIT_SOCKET_BIND); } if (listen(listen_fd, 5) == -1) { pseudo_diag("couldn't listen on socket: %s\n", strerror(errno)); - return 1; + exit(PSEUDO_EXIT_SOCKET_LISTEN); } - if (daemonize) { - if ((rc = fork()) != 0) { - if (rc == -1) { - pseudo_diag("couldn't spawn server: %s\n", strerror(errno)); - return 0; - } - pseudo_debug(PDBGF_SERVER, "started server, pid %d\n", rc); - close(listen_fd); - /* Parent writes pid, that way it's always correct */ - return pseudo_server_write_pid(rc); - } - /* In child */ - pseudo_new_pid(); - fclose(stdin); - fclose(stdout); - pseudo_debug_logfile(PSEUDO_LOGFILE, 2); - } else { - /* Write the pid if we don't daemonize */ - pseudo_server_write_pid(getpid()); + rc = pseudo_server_write_pid(getpid()); + if (rc != 0) { + pseudo_diag("warning: couldn't write pid file.\n"); } - - setsid(); signal(SIGHUP, quit_now); signal(SIGINT, quit_now); signal(SIGALRM, quit_now); signal(SIGQUIT, quit_now); signal(SIGTERM, quit_now); + /* tell parent process to stop waiting */ + if (daemonize) { + pseudo_diag("Setup complete, sending SIGUSR1 to pid %d.\n", + getppid()); + kill(getppid(), SIGUSR1); + } pseudo_server_loop(); return 0; } |