aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Seebach <peter.seebach@windriver.com>2016-03-01 16:21:15 -0600
committerPeter Seebach <peter.seebach@windriver.com>2016-03-01 16:21:24 -0600
commit77ee254a6c974aad9bcab2c58c9ee9e0880c9718 (patch)
tree1a9155a8198005d46693219322e602eabfe2924d
parent00e66ef8792f832776cabb6d0108505d51b96fe7 (diff)
downloadpseudo-77ee254a6c974aad9bcab2c58c9ee9e0880c9718.tar.gz
pseudo-77ee254a6c974aad9bcab2c58c9ee9e0880c9718.tar.bz2
pseudo-77ee254a6c974aad9bcab2c58c9ee9e0880c9718.zip
Server launch reworking.
This is the big overhaul to have the server provide meaningful exit status to clients. In the process, I discovered that the server was running with signals blocked if launched by a client, which is not a good thing, and prevented this from working as intended. Still looking to see why more than one server spawn seems to happen.
-rw-r--r--ChangeLog.txt3
-rw-r--r--enums/exit_status.in16
-rw-r--r--pseudo.c87
-rw-r--r--pseudo_client.c9
-rw-r--r--pseudo_server.c236
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"
diff --git a/pseudo.c b/pseudo.c
index 9ae1ab8..d1f35c3 100644
--- a/pseudo.c
+++ b/pseudo.c
@@ -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;
}