about summary refs log tree commit homepage
path: root/iostat_process.c
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2012-01-11 21:46:04 +0000
committerEric Wong <normalperson@yhbt.net>2012-01-11 21:46:04 +0000
commit301b41b6f1350806a750794d615e3468735757a6 (patch)
tree54deb2b4cb0060a54746e3635746d0f338c294f5 /iostat_process.c
downloadcmogstored-301b41b6f1350806a750794d615e3468735757a6.tar.gz
Nuked old history since it was missing copyright/GPLv3 notices.
Diffstat (limited to 'iostat_process.c')
-rw-r--r--iostat_process.c161
1 files changed, 161 insertions, 0 deletions
diff --git a/iostat_process.c b/iostat_process.c
new file mode 100644
index 0000000..cb63ef7
--- /dev/null
+++ b/iostat_process.c
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2012, Eric Wong <normalperson@yhbt.net>
+ * License: GPLv3 or later (see COPYING for details)
+ */
+/*
+ * process management for iostat(1)
+ * Since iostat(1) watches the entire system, we only spawn it once
+ * regardless of the number of mog_svc objects we have.
+ */
+#include "cmogstored.h"
+#include <sys/wait.h>
+
+static pid_t iostat_pid;
+static time_t iostat_last_fail;
+static struct mog_iostat *iostat;
+static time_t iostat_fail_timeout = 10;
+
+static void iostat_atexit(void)
+{
+        if (iostat_pid > 0)
+                kill(iostat_pid, SIGTERM);
+}
+
+static void iostat_wait(void)
+{
+        int status;
+        pid_t tmp;
+
+        if (iostat_pid <= 0) return;
+
+        tmp = waitpid(iostat_pid, &status, WNOHANG);
+        if (tmp < 0) {
+                syslog(LOG_WARNING, "waitpid(pid=%d) failed: %m",
+                       (int)iostat_pid);
+                return;
+        }
+
+        if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
+                /* syslog(LOG_DEBUG, "iostat done, restarting"); */
+        } else {
+                iostat_last_fail = time(NULL);
+                syslog(LOG_WARNING,
+                       "iostat done (pid=%d, status=%d), will retry in %ds",
+                       (int)iostat_pid, status, (int)iostat_fail_timeout);
+        }
+        iostat_pid = 0;
+}
+
+static int iostat_pipe_init(int *fds)
+{
+        if (pipe2(fds, O_CLOEXEC) < 0) {
+                PRESERVE_ERRNO( syslog(LOG_ERR, "pipe2() failed: %m") );
+                return -1;
+        }
+
+        CHECK(int, 0, set_nonblocking_flag(fds[0], true));
+        /* fds[1] (write end) stays _blocking_ */
+
+        return 0;
+}
+
+/* only called in the child process */
+static const char * exec_cmd(const char *cmd)
+{
+        time_t last_fail = time(NULL) - iostat_last_fail;
+        time_t delay = iostat_fail_timeout - last_fail;
+
+        if (delay <= 0)
+                return xasprintf("exec %s", cmd);
+
+        syslog(LOG_DEBUG,
+               "delaying exec of `%s' for %ds due to previous failure",
+               cmd, (int)delay);
+        return xasprintf("sleep %d; exec %s", (int)delay, cmd);
+}
+
+static void dup2_or_die(int oldfd, int newfd, const char *errdesc)
+{
+        int rc;
+
+        do
+                rc = dup2(oldfd, newfd);
+        while (rc < 0 && (errno == EINTR || errno == EBUSY));
+
+        if (rc < 0) {
+                syslog(LOG_CRIT, "dup2(%s) failed: %m", errdesc);
+                abort();
+        }
+}
+
+static void preexec_redirect(int out_fd)
+{
+        int null_fd;
+
+        dup2_or_die(out_fd, STDOUT_FILENO, "iostat_pipe[1],STDOUT");
+        mog_close(out_fd);
+
+        null_fd = open("/dev/null", O_RDONLY);
+        if (null_fd < 0) {
+                syslog(LOG_CRIT, "open(/dev/null) failed: %m");
+                abort();
+        }
+        dup2_or_die(null_fd, STDIN_FILENO, "/dev/null,STDIN");
+        mog_close(null_fd);
+
+        /* don't touch stderr */
+}
+
+static pid_t iostat_fork_exec(int out_fd)
+{
+        iostat_pid = fork();
+        if (iostat_pid < 0) {
+                syslog(LOG_ERR, "fork() for iostat failed: %m");
+        } else if (iostat_pid > 0) {
+                mog_close(out_fd);
+        } else {
+                /* rely on /bin/sh to parse iostat command-line args */
+                const char *cmd = getenv("MOG_IOSTAT_CMD");
+                if (!cmd) cmd = "iostat -dx 1 30";
+
+                preexec_redirect(out_fd);
+                if (! mog_cloexec_atomic)
+                        mog_cloexec_from(STDERR_FILENO + 1);
+
+                cmd = exec_cmd(cmd);
+                mog_intr_enable();
+                execl("/bin/sh", "sh", "-c", cmd, (char *)NULL);
+                syslog(LOG_CRIT, "execl(%s) failed: %m", cmd);
+                abort();
+        }
+        return iostat_pid;
+}
+
+struct mog_iostat *mog_iostat_spawn(struct mog_queue *queue)
+{
+        int fds[2];
+        struct mog_fd *mfd;
+
+        iostat_wait();
+        if (iostat_pipe_init(fds) < 0)
+                return NULL; /* EMFILE || ENFILE */
+        if (iostat_fork_exec(fds[1]) < 0)
+                return NULL; /* fork() failure */
+
+        assert(fds[0] >= 0 && "invalid FD");
+
+        mfd = mog_fd_get(fds[0]);
+
+        if (iostat == NULL) atexit(iostat_atexit);
+        iostat = &mfd->as.iostat;
+        mfd->fd = fds[0];
+        iostat->queue = queue;
+        mog_iostat_init(iostat);
+
+        mfd->fd_type = MOG_FD_TYPE_IOSTAT;
+        assert((mfd->in_queue = 0) == 0 && "in_queue check");
+        mfd->queue_state = MOG_QUEUE_STATE_NEW;
+        mog_idleq_push(queue, mfd, MOG_QEV_RD);
+
+        return iostat;
+}