diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makemodule.am | 13 | ||||
-rw-r--r-- | lib/include/service.h | 62 | ||||
-rw-r--r-- | lib/include/telinit.h | 20 | ||||
-rw-r--r-- | lib/include/util.h | 58 | ||||
-rw-r--r-- | lib/src/del_srv_list.c | 18 | ||||
-rw-r--r-- | lib/src/delsrv.c | 25 | ||||
-rw-r--r-- | lib/src/enum_by_name.c | 15 | ||||
-rw-r--r-- | lib/src/opensock.c | 33 | ||||
-rw-r--r-- | lib/src/rdline.c | 54 | ||||
-rw-r--r-- | lib/src/rdsrv.c | 249 | ||||
-rw-r--r-- | lib/src/splitkv.c | 124 | ||||
-rw-r--r-- | lib/src/srv_tsort.c | 75 | ||||
-rw-r--r-- | lib/src/srvscan.c | 93 | ||||
-rw-r--r-- | lib/src/strexpand.c | 55 |
14 files changed, 894 insertions, 0 deletions
diff --git a/lib/Makemodule.am b/lib/Makemodule.am new file mode 100644 index 0000000..bd82e81 --- /dev/null +++ b/lib/Makemodule.am @@ -0,0 +1,13 @@ +HEADRS = lib/include/util.h lib/include/service.h lib/include/telinit.h + +libinit_a_SOURCES = lib/src/delsrv.c lib/src/rdline.c +libinit_a_SOURCES += lib/src/splitkv.c lib/src/enum_by_name.c +libinit_a_SOURCES += lib/src/strexpand.c lib/src/rdsrv.c lib/src/srvscan.c +libinit_a_SOURCES += lib/src/del_srv_list.c lib/src/srv_tsort.c +libinit_a_SOURCES += lib/src/opensock.c $(HEADRS) +libinit_a_CPPFLAGS = $(AM_CPPFLAGS) +libinit_a_CFLAGS = $(AM_CFLAGS) + +EXTRA_DIST += $(HEADRS) + +noinst_LIBRARIES += libinit.a diff --git a/lib/include/service.h b/lib/include/service.h new file mode 100644 index 0000000..eb92d85 --- /dev/null +++ b/lib/include/service.h @@ -0,0 +1,62 @@ +#ifndef SERVICE_H +#define SERVICE_H + +#include <sys/types.h> + +enum { + SVC_ONCE = 0, + SVC_WAIT, + SVC_RESPAWN, +}; + +enum { + TGT_BOOT = 0, + TGT_SHUTDOWN, + TGT_REBOOT, + TGT_CAD, + + TGT_MAX +}; + +typedef struct service_t { + int type; /* SVC_* service type */ + int target; /* TGT_* service target */ + char *name; /* canonical service name */ + char *desc; /* description string */ + char **exec; /* command lines to execute */ + size_t num_exec; /* number of command lines */ + char *ctty; /* controlling tty or log file */ + + char **before; + size_t num_before; + char **after; + size_t num_after; + + pid_t pid; + int status; /* process exit status */ + + struct service_t *next; +} service_t; + +typedef struct { + service_t *targets[TGT_MAX]; +} service_list_t; + +/* + Read a service from a file. +*/ +service_t *rdsrv(int dirfd, const char *filename); + +void delsrv(service_t *srv); + +int srvscan(const char *directory, service_list_t *list); + +void del_srv_list(service_list_t *list); + +/* + Sort a list of services by dependencies. +*/ +service_t *srv_tsort(service_t *list); + +#endif /* SERVICE_H */ + diff --git a/lib/include/telinit.h b/lib/include/telinit.h new file mode 100644 index 0000000..72ad871 --- /dev/null +++ b/lib/include/telinit.h @@ -0,0 +1,20 @@ +#ifndef TELINIT_H +#define TELINIT_H + +#include "config.h" + +#define INITSOCK SOCKDIR "/" "initd.socket" + +enum { + TI_SHUTDOWN = 1, + TI_REBOOT = 2, +}; + +typedef struct { + int type; +} ti_msg_t; + +int opensock(void); + +#endif /* TELINIT_H */ + diff --git a/lib/include/util.h b/lib/include/util.h new file mode 100644 index 0000000..bed2ba7 --- /dev/null +++ b/lib/include/util.h @@ -0,0 +1,58 @@ +#ifndef UTIL_H +#define UTIL_H + +#include <sys/types.h> +#include <stdbool.h> +#include <stddef.h> + +#include "config.h" + +#ifdef __GNUC__ + #define NORETURN __attribute__((noreturn)) +#endif + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +typedef struct { + const char *name; + int value; +} enum_map_t; + + +/* + Read from fd until end-of-file or a line feed is encountered. + + Returns NULL with errno set on failure. Returns NULL with errno + cleared if end-of-file is reached. + + The line must be deallocated with free(). +*/ +char *rdline(int fd); + +/* + Split a line of the shape "key = value" into key and value part. + + The key can contain alphanumeric characters and can be padded with + spaces or tabs. + + The value can be either a sequence of alphanumeric characters, period + or underscore OR a string in quotation marks. For strings, the + quotation marks are removed and escape sequences are processed. + + The value may also be padded with spaces or tabs but the line may not + contain anything else after the value, except for spaces, tabs or + the '#' symbol which is interpreted as start of a comment. +*/ +int splitkv(char *line, char **key, char **value); + +/* + Search through an array of enum_map_t entries to resolve a string to + a numeric value. The end of the map is indicated by a sentinel entry + with the name set to NULL. +*/ +const enum_map_t *enum_by_name(const enum_map_t *map, const char *name); + +char *strexpand(const char *inp, size_t argc, const char *const *argv); + +#endif /* UTIL_H */ + diff --git a/lib/src/del_srv_list.c b/lib/src/del_srv_list.c new file mode 100644 index 0000000..916fcb2 --- /dev/null +++ b/lib/src/del_srv_list.c @@ -0,0 +1,18 @@ +#include <stdlib.h> + +#include "service.h" + +void del_srv_list(service_list_t *list) +{ + service_t *srv; + int i; + + for (i = 0; i < TGT_MAX; ++i) { + while (list->targets[i] != NULL) { + srv = list->targets[i]; + list->targets[i] = srv->next; + + delsrv(srv); + } + } +} diff --git a/lib/src/delsrv.c b/lib/src/delsrv.c new file mode 100644 index 0000000..b660558 --- /dev/null +++ b/lib/src/delsrv.c @@ -0,0 +1,25 @@ +#include <stdlib.h> + +#include "service.h" + +void delsrv(service_t *srv) +{ + size_t i; + + for (i = 0; i < srv->num_exec; ++i) + free(srv->exec[i]); + + for (i = 0; i < srv->num_before; ++i) + free(srv->before[i]); + + for (i = 0; i < srv->num_after; ++i) + free(srv->after[i]); + + free(srv->before); + free(srv->after); + free(srv->name); + free(srv->desc); + free(srv->exec); + free(srv->ctty); + free(srv); +} diff --git a/lib/src/enum_by_name.c b/lib/src/enum_by_name.c new file mode 100644 index 0000000..1948eb9 --- /dev/null +++ b/lib/src/enum_by_name.c @@ -0,0 +1,15 @@ +#include <string.h> + +#include "util.h" + +const enum_map_t *enum_by_name(const enum_map_t *map, const char *name) +{ + size_t i; + + for (i = 0; map[i].name != NULL; ++i) { + if (!strcmp(map[i].name, name)) + return map + i; + } + + return NULL; +} diff --git a/lib/src/opensock.c b/lib/src/opensock.c new file mode 100644 index 0000000..06101ef --- /dev/null +++ b/lib/src/opensock.c @@ -0,0 +1,33 @@ +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> + +#include "telinit.h" + +int opensock(void) +{ + struct sockaddr_un un; + int fd; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + perror("socket"); + return -1; + } + + memset(&un, 0, sizeof(un)); + un.sun_family = AF_UNIX; + + strcpy(un.sun_path, INITSOCK); + + if (connect(fd, (struct sockaddr *)&un, sizeof(un))) { + perror("connect: " INITSOCK); + close(fd); + return -1; + } + + return fd; +} diff --git a/lib/src/rdline.c b/lib/src/rdline.c new file mode 100644 index 0000000..591c713 --- /dev/null +++ b/lib/src/rdline.c @@ -0,0 +1,54 @@ +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <ctype.h> + +#include "util.h" + +char *rdline(int fd) +{ + size_t i = 0, bufsiz = 0, newsz; + char c, *new, *buffer = NULL; + int ret; + + for (;;) { + switch (read(fd, &c, 1)) { + case 0: + if (i == 0) { + errno = 0; + return NULL; + } + c = '\0'; + break; + case 1: + if (c == '\n') + c = '\0'; + break; + default: + if (errno == EINTR) + continue; + goto fail; + } + + if (i == bufsiz) { + newsz = bufsiz ? bufsiz * 2 : 16; + new = realloc(buffer, newsz); + + if (new == NULL) + goto fail; + + buffer = new; + bufsiz = newsz; + } + + buffer[i++] = c; + if (c == '\0') + break; + } + return buffer; +fail: + ret = errno; + free(buffer); + errno = ret; + return NULL; +} diff --git a/lib/src/rdsrv.c b/lib/src/rdsrv.c new file mode 100644 index 0000000..52eb19c --- /dev/null +++ b/lib/src/rdsrv.c @@ -0,0 +1,249 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <fcntl.h> + +#include "service.h" +#include "util.h" + +static const enum_map_t type_map[] = { + { "once", SVC_ONCE }, + { "wait", SVC_WAIT }, + { "respawn", SVC_RESPAWN }, + { NULL, 0 }, +}; + +static const enum_map_t target_map[] = { + { "boot", TGT_BOOT }, + { "shutdown", TGT_SHUTDOWN }, + { "reboot", TGT_REBOOT }, + { "ctrlaltdel", TGT_CAD }, + { NULL, 0 }, +}; + +static int srv_name(service_t *srv, char *arg, + const char *filename, size_t lineno) +{ + (void)filename; (void)lineno; + srv->name = arg; + return 0; +} + +static int srv_desc(service_t *srv, char *arg, + const char *filename, size_t lineno) +{ + (void)filename; (void)lineno; + srv->desc = arg; + return 0; +} + +static int srv_tty(service_t *srv, char *arg, + const char *filename, size_t lineno) +{ + (void)filename; (void)lineno; + srv->ctty = arg; + return 0; +} + +static int srv_exec(service_t *srv, char *arg, + const char *filename, size_t lineno) +{ + char **new = realloc(srv->exec, sizeof(char*) * (srv->num_exec + 1)); + + if (new == NULL) { + fprintf(stderr, "%s: %zu: out of memory\n", filename, lineno); + free(arg); + return -1; + } + + srv->exec = new; + srv->exec[srv->num_exec++] = arg; + return 0; +} + +static int srv_before(service_t *srv, char *arg, + const char *filename, size_t lineno) +{ + char **new = realloc(srv->before, + sizeof(char*) * (srv->num_before + 1)); + + if (new == NULL) { + fprintf(stderr, "%s: %zu: out of memory\n", filename, lineno); + free(arg); + return -1; + } + + srv->before = new; + srv->before[srv->num_before++] = arg; + return 0; +} + +static int srv_after(service_t *srv, char *arg, + const char *filename, size_t lineno) +{ + char **new = realloc(srv->after, sizeof(char*) * (srv->num_after + 1)); + + if (new == NULL) { + fprintf(stderr, "%s: %zu: out of memory\n", filename, lineno); + free(arg); + return -1; + } + + srv->after = new; + srv->after[srv->num_after++] = arg; + return 0; +} + +static int srv_type(service_t *srv, char *arg, + const char *filename, size_t lineno) +{ + const enum_map_t *ent = enum_by_name(type_map, arg); + + if (ent == NULL) { + fprintf(stderr, "%s: %zu: unknown service type '%s'\n", + filename, lineno, arg); + return -1; + } + + srv->type = ent->value; + free(arg); + return 0; +} + +static int srv_target(service_t *srv, char *arg, + const char *filename, size_t lineno) +{ + const enum_map_t *ent = enum_by_name(target_map, arg); + + if (ent == NULL) { + fprintf(stderr, "%s: %zu: unknown service target '%s'\n", + filename, lineno, arg); + return -1; + } + + srv->target = ent->value; + free(arg); + return 0; +} + + +static const struct { + const char *key; + + int (*handle)(service_t *srv, char *arg, + const char *filename, size_t lineno); +} srv_params[] = { + { "name", srv_name }, + { "description", srv_desc }, + { "exec", srv_exec }, + { "type", srv_type }, + { "target", srv_target }, + { "tty", srv_tty }, + { "before", srv_before }, + { "after", srv_after }, +}; + + +service_t *rdsrv(int dirfd, const char *filename) +{ + const char *arg, *args[1]; + char *line, *key, *value; + size_t i, argc, lineno; + service_t *srv; + int fd; + + fd = openat(dirfd, filename, O_RDONLY); + if (fd < 0) { + perror(filename); + return NULL; + } + + arg = strchr(filename, '@'); + if (arg != NULL) { + args[0] = arg + 1; + argc = 1; + } else { + argc = 0; + } + + srv = calloc(1, sizeof(*srv)); + if (srv == NULL) { + fputs("out of memory\n", stderr); + close(fd); + return NULL; + } + + for (lineno = 1; ; ++lineno) { + errno = 0; + line = rdline(fd); + + if (line == NULL) { + if (errno != 0) { + fprintf(stderr, "read: %s: %zu: %s\n", + filename, lineno, strerror(errno)); + goto fail; + } + break; + } + + if (splitkv(line, &key, &value)) { + if (key == NULL) { + fprintf(stderr, + "%s: %zu: expected <key> = <value>\n", + filename, lineno); + } else if (value == NULL) { + fprintf(stderr, + "%s: %zu: expected value after %s\n", + filename, lineno, key); + } else { + fprintf(stderr, + "%s: %zu: unexpected arguments " + "after key-value pair\n", + filename, lineno); + } + goto fail; + } + + if (key == NULL) { + free(line); + continue; + } + + for (i = 0; i < ARRAY_SIZE(srv_params); ++i) { + if (!strcmp(srv_params[i].key, key)) + break; + } + + if (i >= ARRAY_SIZE(srv_params)) { + fprintf(stderr, "%s: %zu: unknown parameter '%s'\n", + filename, lineno, key); + goto fail_line; + } + + value = strexpand(value, argc, args); + if (value == NULL) { + fputs("out of memory", stderr); + goto fail_line; + } + + if (srv_params[i].handle(srv, value, filename, lineno)) { + free(value); + goto fail_line; + } + + free(line); + } + + close(fd); + return srv; +fail_line: + free(line); +fail: + close(fd); + delsrv(srv); + return NULL; +} diff --git a/lib/src/splitkv.c b/lib/src/splitkv.c new file mode 100644 index 0000000..6c6fe94 --- /dev/null +++ b/lib/src/splitkv.c @@ -0,0 +1,124 @@ +#include <ctype.h> + +#include "util.h" + +static char *skpspc(char *ptr) +{ + while (*ptr == ' ' || *ptr == '\t') + ++ptr; + return ptr; +} + +static int xdigit(int x) +{ + if (isupper(x)) + return x - 'A' + 0x0A; + if (islower(x)) + return x - 'a' + 0x0A; + return x - '0'; +} + +static char *parse_str(char *src) +{ + char *dst = src; + int c; + + for (;;) { + c = *(src++); + + switch (c) { + case '\\': + c = *(src++); + + switch (c) { + case 'a': c = '\a'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 't': c = '\t'; break; + case '\\': break; + case '"': break; + case 'x': + c = 0; + if (isxdigit(*src)) + c = (c << 4) | xdigit(*(src++)); + if (isxdigit(*src)) + c = (c << 4) | xdigit(*(src++)); + break; + case '0': + c = 0; + if (isdigit(*src) && *src < '8') + c = (c << 3) | (*(src++) - '0'); + if (isdigit(*src) && *src < '8') + c = (c << 3) | (*(src++) - '0'); + if (isdigit(*src) && *src < '8') + c = (c << 3) | (*(src++) - '0'); + break; + default: + return NULL; + } + break; + case '"': + *(dst++) = '\0'; + goto out; + } + + *(dst++) = c; + } +out: + return src; +} + +int splitkv(char *line, char **key, char **value) +{ + *key = NULL; + *value = NULL; + + line = skpspc(line); + + if (*line == '#' || *line == '\0') + return 0; + + if (!isalpha(*line)) + return -1; + + *key = line; + + while (isalnum(*line)) + ++line; + + if (*line == ' ' || *line == '\t') { + *(line++) = '\0'; + line = skpspc(line); + } + + if (*line != '=') + return -1; + + *(line++) = '\0'; + line = skpspc(line); + + if (*line == '"') { + ++line; + *value = line; + + line = parse_str(line); + } else if (isalnum(*line)) { + *value = line; + + while (isalnum(*line) || *line == '.' || *line == '_') + ++line; + + if (*line != '\0') + *(line++) = '\0'; + } else { + return -1; + } + + line = skpspc(line); + + if (*line != '\0' && *line != '#') + return -1; + + return 0; +} diff --git a/lib/src/srv_tsort.c b/lib/src/srv_tsort.c new file mode 100644 index 0000000..e2549b1 --- /dev/null +++ b/lib/src/srv_tsort.c @@ -0,0 +1,75 @@ +#include <stdbool.h> +#include <stddef.h> +#include <string.h> +#include <errno.h> + +#include "service.h" + +static bool has_dependencies(service_t *list, service_t *svc) +{ + size_t i; + + while (list != NULL) { + for (i = 0; i < svc->num_after; ++i) { + if (!strcmp(svc->after[i], list->name)) + return true; + } + + for (i = 0; i < list->num_before; ++i) { + if (!strcmp(list->before[i], svc->name)) + return true; + } + + list = list->next; + } + + return false; +} + +service_t *srv_tsort(service_t *list) +{ + service_t *nl = NULL, *end = NULL; + service_t *svc, *prev; + + while (list != NULL) { + /* remove first service without dependencies */ + prev = NULL; + svc = list; + + while (svc != NULL) { + if (has_dependencies(list, svc)) { + prev = svc; + svc = svc->next; + } else { + if (prev != NULL) { + prev->next = svc->next; + } else { + list = svc->next; + } + svc->next = NULL; + break; + } + } + + /* cycle! */ + if (svc == NULL) { + if (end == NULL) { + nl = list; + } else { + end->next = list; + } + errno = ELOOP; + break; + } + + /* append to new list */ + if (end == NULL) { + nl = end = svc; + } else { + end->next = svc; + end = svc; + } + } + + return nl; +} diff --git a/lib/src/srvscan.c b/lib/src/srvscan.c new file mode 100644 index 0000000..71ad4f9 --- /dev/null +++ b/lib/src/srvscan.c @@ -0,0 +1,93 @@ +#include <sys/types.h> +#include <sys/stat.h> +#include <stddef.h> +#include <dirent.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <fcntl.h> +#include <ctype.h> + +#include "service.h" + +int srvscan(const char *directory, service_list_t *list) +{ + int i, dfd, type, ret = 0; + struct dirent *ent; + const char *ptr; + service_t *srv; + struct stat sb; + DIR *dir; + + for (i = 0; i < TGT_MAX; ++i) + list->targets[i] = NULL; + + dir = opendir(directory); + if (dir == NULL) { + perror(directory); + return -1; + } + + dfd = dirfd(dir); + if (dfd < 0) { + perror(directory); + closedir(dir); + return -1; + } + + for (;;) { + errno = 0; + ent = readdir(dir); + + if (ent == NULL) { + if (errno != 0) { + perror(directory); + ret = -1; + } + break; + } + + for (ptr = ent->d_name; isalnum(*ptr) || *ptr == '_'; ++ptr) + ; + + if (*ptr != '\0' && *ptr != '@') + continue; + + if (fstatat(dfd, ent->d_name, &sb, AT_SYMLINK_NOFOLLOW)) { + fprintf(stderr, "stat %s/%s: %s\n", + directory, ent->d_name, strerror(errno)); + ret = -1; + continue; + } + + type = (sb.st_mode & S_IFMT); + + if (type != S_IFREG && type != S_IFLNK) + continue; + + srv = rdsrv(dfd, ent->d_name); + if (srv == NULL) { + ret = -1; + continue; + } + + srv->next = list->targets[srv->target]; + list->targets[srv->target] = srv; + } + + for (i = 0; i < TGT_MAX; ++i) { + if (list->targets[i] == NULL) + continue; + + errno = 0; + list->targets[i] = srv_tsort(list->targets[i]); + + if (errno != 0) { + fprintf(stderr, "sorting services read from %s: %s\n", + directory, strerror(errno)); + } + } + + closedir(dir); + return ret; +} diff --git a/lib/src/strexpand.c b/lib/src/strexpand.c new file mode 100644 index 0000000..7e552eb --- /dev/null +++ b/lib/src/strexpand.c @@ -0,0 +1,55 @@ +#include <string.h> +#include <stdlib.h> +#include <ctype.h> + +#include "util.h" + +char *strexpand(const char *inp, size_t argc, const char *const *argv) +{ + char *out, *dst; + const char *ptr; + size_t i, len; + + ptr = inp; + len = 0; + + while (*ptr != '\0') { + if (ptr[0] == '%' && isdigit(ptr[1])) { + i = ptr[1] - '0'; + if (i < argc) + len += strlen(argv[i]); + ptr += 2; + } else if (ptr[0] == '%' && ptr[1] == '%') { + ptr += 2; + len += 1; + } else { + ++ptr; + ++len; + } + } + + out = calloc(1, len + 1); + if (out == NULL) + return NULL; + + dst = out; + + while (*inp != '\0') { + if (inp[0] == '%' && isdigit(inp[1])) { + i = inp[1] - '0'; + if (i < argc) { + len = strlen(argv[i]); + memcpy(dst, argv[i], len); + dst += len; + } + inp += 2; + } else if (inp[0] == '%' && inp[1] == '%') { + *(dst++) = '%'; + inp += 2; + } else { + *(dst++) = *(inp++); + } + } + + return out; +} |