service: don't use stdio log channel
[project/procd.git] / service / instance.c
index 5ac7d57..778b6be 100644 (file)
 #include <unistd.h>
 #include <stdint.h>
 #include <fcntl.h>
+#include <pwd.h>
+#include <libgen.h>
+
+#include <libubox/md5.h>
 
 #include "../procd.h"
 
 #include "service.h"
 #include "instance.h"
 
-#include "../utils/md5.h"
 
 enum {
        INSTANCE_ATTR_COMMAND,
@@ -36,6 +39,12 @@ enum {
        INSTANCE_ATTR_TRIGGER,
        INSTANCE_ATTR_RESPAWN,
        INSTANCE_ATTR_NICE,
+       INSTANCE_ATTR_LIMITS,
+       INSTANCE_ATTR_WATCH,
+       INSTANCE_ATTR_ERROR,
+       INSTANCE_ATTR_USER,
+       INSTANCE_ATTR_STDOUT,
+       INSTANCE_ATTR_STDERR,
        __INSTANCE_ATTR_MAX
 };
 
@@ -48,6 +57,12 @@ static const struct blobmsg_policy instance_attr[__INSTANCE_ATTR_MAX] = {
        [INSTANCE_ATTR_TRIGGER] = { "triggers", BLOBMSG_TYPE_ARRAY },
        [INSTANCE_ATTR_RESPAWN] = { "respawn", BLOBMSG_TYPE_ARRAY },
        [INSTANCE_ATTR_NICE] = { "nice", BLOBMSG_TYPE_INT32 },
+       [INSTANCE_ATTR_LIMITS] = { "limits", BLOBMSG_TYPE_TABLE },
+       [INSTANCE_ATTR_WATCH] = { "watch", BLOBMSG_TYPE_ARRAY },
+       [INSTANCE_ATTR_ERROR] = { "error", BLOBMSG_TYPE_ARRAY },
+       [INSTANCE_ATTR_USER] = { "user", BLOBMSG_TYPE_STRING },
+       [INSTANCE_ATTR_STDOUT] = { "stdout", BLOBMSG_TYPE_BOOL },
+       [INSTANCE_ATTR_STDERR] = { "stderr", BLOBMSG_TYPE_BOOL },
 };
 
 struct instance_netdev {
@@ -60,14 +75,75 @@ struct instance_file {
        uint32_t md5[4];
 };
 
+struct rlimit_name {
+       const char *name;
+       int resource;
+};
+
+static const struct rlimit_name rlimit_names[] = {
+       { "as", RLIMIT_AS },
+       { "core", RLIMIT_CORE },
+       { "cpu", RLIMIT_CPU },
+       { "data", RLIMIT_DATA },
+       { "fsize", RLIMIT_FSIZE },
+       { "memlock", RLIMIT_MEMLOCK },
+       { "msgqueue", RLIMIT_MSGQUEUE },
+       { "nice", RLIMIT_NICE },
+       { "nofile", RLIMIT_NOFILE },
+       { "nproc", RLIMIT_NPROC },
+       { "rss", RLIMIT_RSS },
+       { "rtprio", RLIMIT_RTPRIO },
+       { "sigpending", RLIMIT_SIGPENDING },
+       { "stack", RLIMIT_STACK },
+       { NULL, 0 }
+};
+
+static void closefd(int fd)
+{
+       if (fd > STDERR_FILENO)
+               close(fd);
+}
+
 static void
-instance_run(struct service_instance *in)
+instance_limits(const char *limit, const char *value)
+{
+       int i;
+       struct rlimit rlim;
+       unsigned long cur, max;
+
+       for (i = 0; rlimit_names[i].name != NULL; i++) {
+               if (strcmp(rlimit_names[i].name, limit))
+                       continue;
+               if (!strcmp(value, "unlimited")) {
+                       rlim.rlim_cur = RLIM_INFINITY;
+                       rlim.rlim_max = RLIM_INFINITY;
+               } else {
+                       if (getrlimit(rlimit_names[i].resource, &rlim))
+                               return;
+
+                       cur = rlim.rlim_cur;
+                       max = rlim.rlim_max;
+
+                       if (sscanf(value, "%lu %lu", &cur, &max) < 1)
+                               return;
+
+                       rlim.rlim_cur = cur;
+                       rlim.rlim_max = max;
+               }
+
+               setrlimit(rlimit_names[i].resource, &rlim);
+               return;
+       }
+}
+
+static void
+instance_run(struct service_instance *in, int stdout, int stderr)
 {
        struct blobmsg_list_node *var;
        struct blob_attr *cur;
        char **argv;
        int argc = 1; /* NULL terminated */
-       int rem, fd;
+       int rem, stdin;
 
        if (in->nice)
                setpriority(PRIO_PROCESS, 0, in->nice);
@@ -78,6 +154,9 @@ instance_run(struct service_instance *in)
        blobmsg_list_for_each(&in->env, var)
                setenv(blobmsg_name(var->data), blobmsg_data(var->data), 1);
 
+       blobmsg_list_for_each(&in->limits, var)
+               instance_limits(blobmsg_name(var->data), blobmsg_data(var->data));
+
        argv = alloca(sizeof(char *) * argc);
        argc = 0;
 
@@ -85,13 +164,31 @@ instance_run(struct service_instance *in)
                argv[argc++] = blobmsg_data(cur);
 
        argv[argc] = NULL;
-       fd = open("/dev/null", O_RDWR);
-       if (fd > -1) {
-               dup2(fd, STDIN_FILENO);
-               dup2(fd, STDOUT_FILENO);
-               dup2(fd, STDERR_FILENO);
-               if (fd > STDERR_FILENO)
-                       close(fd);
+
+       stdin = open("/dev/null", O_RDONLY);
+
+       if (stdout == -1)
+               stdout = open("/dev/null", O_WRONLY);
+
+       if (stderr == -1)
+               stderr = open("/dev/null", O_WRONLY);
+
+       if (stdin > -1) {
+               dup2(stdin, STDIN_FILENO);
+               closefd(stdin);
+       }
+       if (stdout > -1) {
+               dup2(stdout, STDOUT_FILENO);
+               closefd(stdout);
+       }
+       if (stderr > -1) {
+               dup2(stderr, STDERR_FILENO);
+               closefd(stderr);
+       }
+
+       if (in->uid || in->gid) {
+               setuid(in->uid);
+               setgid(in->gid);
        }
        execvp(argv[0], argv);
        exit(127);
@@ -101,10 +198,31 @@ void
 instance_start(struct service_instance *in)
 {
        int pid;
+       int opipe[2] = { -1, -1 };
+       int epipe[2] = { -1, -1 };
+
+       if (!avl_is_empty(&in->errors.avl)) {
+               LOG("Not starting instance %s::%s, an error was indicated\n", in->srv->name, in->name);
+               return;
+       }
 
        if (in->proc.pending)
                return;
 
+       if (in->stdout.fd.fd > -2) {
+               if (pipe(opipe)) {
+                       ULOG_WARN("pipe() failed: %d (%s)\n", errno, strerror(errno));
+                       opipe[0] = opipe[1] = -1;
+               }
+       }
+
+       if (in->stderr.fd.fd > -2) {
+               if (pipe(epipe)) {
+                       ULOG_WARN("pipe() failed: %d (%s)\n", errno, strerror(errno));
+                       epipe[0] = epipe[1] = -1;
+               }
+       }
+
        in->restart = false;
        in->halt = !in->respawn;
 
@@ -117,7 +235,9 @@ instance_start(struct service_instance *in)
 
        if (!pid) {
                uloop_done();
-               instance_run(in);
+               closefd(opipe[0]);
+               closefd(epipe[0]);
+               instance_run(in, opipe[1], epipe[1]);
                return;
        }
 
@@ -125,6 +245,61 @@ instance_start(struct service_instance *in)
        in->proc.pid = pid;
        clock_gettime(CLOCK_MONOTONIC, &in->start);
        uloop_process_add(&in->proc);
+
+       if (opipe[0] > -1) {
+               ustream_fd_init(&in->stdout, opipe[0]);
+               closefd(opipe[1]);
+       }
+
+       if (epipe[0] > -1) {
+               ustream_fd_init(&in->stderr, epipe[0]);
+               closefd(epipe[1]);
+       }
+
+       service_event("instance.start", in->srv->name, in->name);
+}
+
+static void
+instance_stdio(struct ustream *s, int prio, struct service_instance *in)
+{
+       char *newline, *str, *arg0, ident[32];
+       int len;
+
+       do {
+               str = ustream_get_read_buf(s, NULL);
+               if (!str)
+                       break;
+
+               newline = strchr(str, '\n');
+               if (!newline)
+                       break;
+
+               *newline = 0;
+               len = newline + 1 - str;
+
+               arg0 = basename(blobmsg_data(blobmsg_data(in->command)));
+               snprintf(ident, sizeof(ident), "%s[%d]", arg0, in->proc.pid);
+
+               ulog_open(ULOG_SYSLOG, LOG_DAEMON, ident);
+               ulog(prio, "%s\n", str);
+               ulog_open(ULOG_SYSLOG, LOG_DAEMON, "procd");
+
+               ustream_consume(s, len);
+       } while (1);
+}
+
+static void
+instance_stdout(struct ustream *s, int bytes)
+{
+       instance_stdio(s, LOG_INFO,
+                      container_of(s, struct service_instance, stdout.stream));
+}
+
+static void
+instance_stderr(struct ustream *s, int bytes)
+{
+       instance_stdio(s, LOG_ERR,
+                      container_of(s, struct service_instance, stderr.stream));
 }
 
 static void
@@ -164,7 +339,7 @@ instance_exit(struct uloop_process *p, int ret)
                        in->respawn_count++;
                else
                        in->respawn_count = 0;
-               if (in->respawn_count > in->respawn_retry) {
+               if (in->respawn_count > in->respawn_retry && in->respawn_retry > 0 ) {
                        LOG("Instance %s::%s s in a crash loop %d crashes, %ld seconds since last crash\n",
                                                                in->srv->name, in->name, in->respawn_count, runtime);
                        in->restart = in->respawn = 0;
@@ -173,6 +348,7 @@ instance_exit(struct uloop_process *p, int ret)
                        uloop_timeout_set(&in->timeout, in->respawn_timeout * 1000);
                }
        }
+       service_event("instance.stop", in->srv->name, in->name);
 }
 
 void
@@ -219,6 +395,18 @@ instance_config_changed(struct service_instance *in, struct service_instance *in
        if (in->nice != in_new->nice)
                return true;
 
+       if (in->uid != in_new->uid)
+               return true;
+
+       if (in->gid != in_new->gid)
+               return true;
+
+       if (!blobmsg_list_equal(&in->limits, &in_new->limits))
+               return true;
+
+       if (!blobmsg_list_equal(&in->errors, &in_new->errors))
+               return true;
+
        return false;
 }
 
@@ -281,6 +469,15 @@ instance_file_update(struct blobmsg_list_node *l)
        close(fd);
 }
 
+static void
+instance_fill_any(struct blobmsg_list *l, struct blob_attr *cur)
+{
+       if (!cur)
+               return;
+
+       blobmsg_list_fill(l, blobmsg_data(cur), blobmsg_data_len(cur), false);
+}
+
 static bool
 instance_fill_array(struct blobmsg_list *l, struct blob_attr *cur, blobmsg_update_cb cb, bool array)
 {
@@ -344,23 +541,42 @@ instance_config_parse(struct service_instance *in)
                in->respawn_retry = vals[2];
        }
        if (tb[INSTANCE_ATTR_TRIGGER]) {
-               in->trigger = malloc(blob_pad_len(tb[INSTANCE_ATTR_TRIGGER]));
-               if (!in->trigger)
-                       return -1;
-               memcpy(in->trigger, tb[INSTANCE_ATTR_TRIGGER], blob_pad_len(tb[INSTANCE_ATTR_TRIGGER]));
+               in->trigger = tb[INSTANCE_ATTR_TRIGGER];
                trigger_add(in->trigger, in);
        }
 
+       if (tb[INSTANCE_ATTR_WATCH]) {
+               blobmsg_for_each_attr(cur2, tb[INSTANCE_ATTR_WATCH], rem) {
+                       if (blobmsg_type(cur2) != BLOBMSG_TYPE_STRING)
+                               continue;
+                       DEBUG(3, "watch for %s\n", blobmsg_get_string(cur2));
+                       watch_add(blobmsg_get_string(cur2), in);
+               }
+       }
+
        if ((cur = tb[INSTANCE_ATTR_NICE])) {
                in->nice = (int8_t) blobmsg_get_u32(cur);
                if (in->nice < -20 || in->nice > 20)
                        return false;
        }
 
-       if (!instance_fill_array(&in->env, tb[INSTANCE_ATTR_ENV], NULL, false))
-               return false;
+       if (tb[INSTANCE_ATTR_USER]) {
+               struct passwd *p = getpwnam(blobmsg_get_string(tb[INSTANCE_ATTR_USER]));
+               if (p) {
+                       in->uid = p->pw_uid;
+                       in->gid = p->pw_gid;
+               }
+       }
+
+       if (tb[INSTANCE_ATTR_STDOUT] && blobmsg_get_bool(tb[INSTANCE_ATTR_STDOUT]))
+               in->stdout.fd.fd = -1;
+
+       if (tb[INSTANCE_ATTR_STDERR] && blobmsg_get_bool(tb[INSTANCE_ATTR_STDERR]))
+               in->stderr.fd.fd = -1;
+
+       instance_fill_any(&in->data, tb[INSTANCE_ATTR_DATA]);
 
-       if (!instance_fill_array(&in->data, tb[INSTANCE_ATTR_DATA], NULL, false))
+       if (!instance_fill_array(&in->env, tb[INSTANCE_ATTR_ENV], NULL, false))
                return false;
 
        if (!instance_fill_array(&in->netdev, tb[INSTANCE_ATTR_NETDEV], instance_netdev_update, true))
@@ -369,6 +585,12 @@ instance_config_parse(struct service_instance *in)
        if (!instance_fill_array(&in->file, tb[INSTANCE_ATTR_FILE], instance_file_update, true))
                return false;
 
+       if (!instance_fill_array(&in->limits, tb[INSTANCE_ATTR_LIMITS], NULL, false))
+               return false;
+
+       if (!instance_fill_array(&in->errors, tb[INSTANCE_ATTR_ERROR], NULL, true))
+               return false;
+
        return true;
 }
 
@@ -378,6 +600,9 @@ instance_config_cleanup(struct service_instance *in)
        blobmsg_list_free(&in->env);
        blobmsg_list_free(&in->data);
        blobmsg_list_free(&in->netdev);
+       blobmsg_list_free(&in->file);
+       blobmsg_list_free(&in->limits);
+       blobmsg_list_free(&in->errors);
 }
 
 static void
@@ -387,6 +612,9 @@ instance_config_move(struct service_instance *in, struct service_instance *in_sr
        blobmsg_list_move(&in->env, &in_src->env);
        blobmsg_list_move(&in->data, &in_src->data);
        blobmsg_list_move(&in->netdev, &in_src->netdev);
+       blobmsg_list_move(&in->file, &in_src->file);
+       blobmsg_list_move(&in->limits, &in_src->limits);
+       blobmsg_list_move(&in->errors, &in_src->errors);
        in->trigger = in_src->trigger;
        in->command = in_src->command;
        in->name = in_src->name;
@@ -421,10 +649,20 @@ instance_update(struct service_instance *in, struct service_instance *in_new)
 void
 instance_free(struct service_instance *in)
 {
+       if (in->stdout.fd.fd > -1) {
+               ustream_free(&in->stdout.stream);
+               close(in->stdout.fd.fd);
+       }
+
+       if (in->stderr.fd.fd > -1) {
+               ustream_free(&in->stderr.stream);
+               close(in->stderr.fd.fd);
+       }
+
        uloop_process_delete(&in->proc);
        uloop_timeout_cancel(&in->timeout);
        trigger_del(in);
-       free(in->trigger);
+       watch_del(in);
        instance_config_cleanup(in);
        free(in->config);
        free(in);
@@ -440,10 +678,20 @@ instance_init(struct service_instance *in, struct service *s, struct blob_attr *
        in->timeout.cb = instance_timeout;
        in->proc.cb = instance_exit;
 
+       in->stdout.fd.fd = -2;
+       in->stdout.stream.string_data = true;
+       in->stdout.stream.notify_read = instance_stdout;
+
+       in->stderr.fd.fd = -2;
+       in->stderr.stream.string_data = true;
+       in->stderr.stream.notify_read = instance_stderr;
+
        blobmsg_list_init(&in->netdev, struct instance_netdev, node, instance_netdev_cmp);
        blobmsg_list_init(&in->file, struct instance_file, node, instance_file_cmp);
        blobmsg_list_simple_init(&in->env);
        blobmsg_list_simple_init(&in->data);
+       blobmsg_list_simple_init(&in->limits);
+       blobmsg_list_simple_init(&in->errors);
        in->valid = instance_config_parse(in);
 }
 
@@ -457,6 +705,14 @@ void instance_dump(struct blob_buf *b, struct service_instance *in, int verbose)
                blobmsg_add_u32(b, "pid", in->proc.pid);
        blobmsg_add_blob(b, in->command);
 
+       if (!avl_is_empty(&in->errors.avl)) {
+               struct blobmsg_list_node *var;
+               void *e = blobmsg_open_array(b, "errors");
+               blobmsg_list_for_each(&in->errors, var)
+                       blobmsg_add_string(b, NULL, blobmsg_data(var->data));
+               blobmsg_close_table(b, e);
+       }
+
        if (!avl_is_empty(&in->env.avl)) {
                struct blobmsg_list_node *var;
                void *e = blobmsg_open_table(b, "env");
@@ -465,6 +721,22 @@ void instance_dump(struct blob_buf *b, struct service_instance *in, int verbose)
                blobmsg_close_table(b, e);
        }
 
+       if (!avl_is_empty(&in->data.avl)) {
+               struct blobmsg_list_node *var;
+               void *e = blobmsg_open_table(b, "data");
+               blobmsg_list_for_each(&in->data, var)
+                       blobmsg_add_blob(b, var->data);
+               blobmsg_close_table(b, e);
+       }
+
+       if (!avl_is_empty(&in->limits.avl)) {
+               struct blobmsg_list_node *var;
+               void *e = blobmsg_open_table(b, "limits");
+               blobmsg_list_for_each(&in->limits, var)
+                       blobmsg_add_string(b, blobmsg_name(var->data), blobmsg_data(var->data));
+               blobmsg_close_table(b, e);
+       }
+
        if (in->respawn) {
                void *r = blobmsg_open_table(b, "respawn");
                blobmsg_add_u32(b, "timeout", in->respawn_timeout);