From eca69b7ccd7190cdbc390d1053885f4ac8bd1ca6 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Mon, 27 Jul 2020 20:16:29 +0300 Subject: [PATCH 1/4] Initial implementation for service sharing This is a very basic implementation of service sharing between strata, it simply forwards systemd services to /bedrock/cross/services/ and replaces the Start commands in them with strat-using ones. Somehow, the security features of "systemd" seem to not bother it now, even though previously in my testing they did. After symlinking a service from cross to /etc/systemd/ it works in the main stratum. --- src/crossfs/crossfs.c | 267 ++++++++++++++++------------ src/slash-bedrock/etc/bedrock.conf | 3 + src/slash-bedrock/share/common-code | 10 +- 3 files changed, 169 insertions(+), 111 deletions(-) diff --git a/src/crossfs/crossfs.c b/src/crossfs/crossfs.c index 8739bd4e..0d52e13a 100644 --- a/src/crossfs/crossfs.c +++ b/src/crossfs/crossfs.c @@ -278,6 +278,8 @@ enum filter { * Combine fonts.dir and fonts.aliases files. */ FILTER_FONT, + + FILTER_SERVICE, /* * Pass file through unaltered. */ @@ -289,6 +291,7 @@ const char *const filter_str[] = { "bin-restrict", "ini", "font", + "service", "pass", }; @@ -1715,6 +1718,60 @@ static inline int set_local_stratum(void) return 0; } +static inline void getattr_ini(struct cfg_entry *cfg, const char *ipath, size_t ipath_len, struct stat *stbuf, int *rv) { + if (!S_ISREG(stbuf->st_mode)) { + return; + } + + struct back_entry *back; + char bpath[PATH_MAX]; + *rv = loc_first_bpath(cfg, ipath, ipath_len, &back, bpath); + if (rv < 0) { + *rv = -errno; + + return; + } + + FILE *fp = fchroot_fopen_rdonly(deref(back)->root_fd, bpath); + if (fp == NULL) { + *rv = -errno; + + return; + } + + char line[PATH_MAX]; + while (fgets(line, sizeof(line), fp) != NULL) { + for (size_t i = 0; i < ARRAY_LEN(ini_inject_strat_str); + i++) { + /* + * No ini_inject_strat_len will exceed line's PATH_MAX, + * this should be safe. + */ + if (strncmp(line, ini_inject_strat_str[i], + ini_inject_strat_len[i]) != 0) { + continue; + } + stbuf->st_size += STRAT_PATH_LEN; + stbuf->st_size += strlen(" "); + stbuf->st_size += deref(back)->name_len; + stbuf->st_size += strlen(" "); + break; + } + for (size_t i = 0; i < ARRAY_LEN(ini_expand_path_str); + i++) { + if (strncmp(line, ini_expand_path_str[i], + ini_expand_path_len[i]) != 0 + || line[ini_expand_path_len[i]] != + '/') { + continue; + } + stbuf->st_size += STRATA_ROOT_LEN; + stbuf->st_size += deref(back)->name_len; + } + } + fclose(fp); +} + static inline int getattr_back(struct cfg_entry *cfg, const char *ipath, size_t ipath_len, struct stat *stbuf) { @@ -1741,11 +1798,8 @@ static inline int getattr_back(struct cfg_entry *cfg, const char *ipath, } break; - case FILTER_INI: - if (!S_ISREG(stbuf->st_mode)) { - break; - } - + case FILTER_SERVICE: + ; struct back_entry *back; char bpath[PATH_MAX]; rv = loc_first_bpath(cfg, ipath, ipath_len, &back, bpath); @@ -1754,45 +1808,15 @@ static inline int getattr_back(struct cfg_entry *cfg, const char *ipath, break; } - FILE *fp = fchroot_fopen_rdonly(deref(back)->root_fd, bpath); - if (fp == NULL) { - rv = -errno; - break; + if (strstr(bpath, "systemd")) { + getattr_ini(cfg, ipath, ipath_len, stbuf, &rv); } - char line[PATH_MAX]; - while (fgets(line, sizeof(line), fp) != NULL) { - for (size_t i = 0; i < ARRAY_LEN(ini_inject_strat_str); - i++) { - /* - * No ini_inject_strat_len will exceed line's PATH_MAX, - * this should be safe. - */ - if (strncmp(line, ini_inject_strat_str[i], - ini_inject_strat_len[i]) != 0) { - continue; - } - stbuf->st_size += STRAT_PATH_LEN; - stbuf->st_size += strlen(" "); - stbuf->st_size += deref(back)->name_len; - stbuf->st_size += strlen(" "); - break; - } - for (size_t i = 0; i < ARRAY_LEN(ini_expand_path_str); - i++) { - if (strncmp(line, ini_expand_path_str[i], - ini_expand_path_len[i]) != 0 - || line[ini_expand_path_len[i]] != - '/') { - continue; - } - stbuf->st_size += STRATA_ROOT_LEN; - stbuf->st_size += deref(back)->name_len; - } - } - fclose(fp); break; + case FILTER_INI: + getattr_ini(cfg, ipath, ipath_len, stbuf, &rv); + break; case FILTER_FONT: ; /* @@ -2078,6 +2102,89 @@ static inline int read_pass(struct cfg_entry *cfg, const char *const ipath, return rv; } +static inline int inject_ini(struct cfg_entry *cfg, const char *ipath, size_t ipath_len, char *buf, size_t size, off_t offset) { + struct back_entry *back; + char bpath[PATH_MAX]; + int rv = loc_first_bpath(cfg, ipath, ipath_len, &back, bpath); + if (rv < 0) { + return -errno; + } + + FILE *fp = fchroot_fopen_rdonly(deref(back)->root_fd, bpath); + if (fp == NULL) { + return -errno; + } + + size_t wrote = 0; + char line[PATH_MAX]; + if (offset < 0) { + return -EINVAL; + } + + size_t off = offset; + while (fgets(line, sizeof(line), fp) != NULL) { + int found = 0; + for (size_t i = 0; i < ARRAY_LEN(ini_inject_strat_str); + i++) { + if (strncmp(line, ini_inject_strat_str[i], + ini_inject_strat_len[i]) != 0) { + continue; + } + strcatoff(buf, ini_inject_strat_str[i], + ini_inject_strat_len[i], &off, &wrote, + size); + strcatoff(buf, STRAT_PATH, STRAT_PATH_LEN, &off, + &wrote, size); + strcatoff(buf, " ", 1, &off, &wrote, size); + strcatoff(buf, deref(back)->name, + deref(back)->name_len, &off, &wrote, + size); + strcatoff(buf, " ", 1, &off, &wrote, size); + strcatoff(buf, line + ini_inject_strat_len[i], + strlen(line + ini_inject_strat_len[i]), + &off, &wrote, size); + found = 1; + break; + } + for (size_t i = 0; i < ARRAY_LEN(ini_expand_path_str); + i++) { + if (strncmp(line, ini_expand_path_str[i], + ini_expand_path_len[i]) != 0 + || line[ini_expand_path_len[i]] != + '/') { + continue; + } + strcatoff(buf, ini_expand_path_str[i], + ini_expand_path_len[i], &off, &wrote, + size); + strcatoff(buf, STRATA_ROOT, STRATA_ROOT_LEN, + &off, &wrote, size); + strcatoff(buf, deref(back)->name, + deref(back)->name_len, &off, &wrote, + size); + strcatoff(buf, line + ini_expand_path_len[i], + strlen(line + ini_expand_path_len[i]), + &off, &wrote, size); + found = 1; + } + if (!found) { + strcatoff(buf, line, strlen(line), &off, + &wrote, size); + } + if (wrote >= size) { + break; + } + } + rv = wrote; + fclose(fp); + + return rv; +} + +static inline int read_systemd_service(struct cfg_entry *cfg, const char *const ipath, size_t ipath_len, char *buf, size_t size, off_t offset) { + return inject_ini(cfg, ipath, ipath_len, buf, size, offset); +} + static inline int read_back(struct cfg_entry *cfg, const char *ipath, size_t ipath_len, char *buf, size_t size, off_t offset) { @@ -2089,7 +2196,7 @@ static inline int read_back(struct cfg_entry *cfg, const char *ipath, size_t rv = pread(bouncer_fd, buf, size, offset); break; - case FILTER_INI: + case FILTER_SERVICE: ; struct back_entry *back; char bpath[PATH_MAX]; @@ -2099,74 +2206,16 @@ static inline int read_back(struct cfg_entry *cfg, const char *ipath, size_t break; } - FILE *fp = fchroot_fopen_rdonly(deref(back)->root_fd, bpath); - if (fp == NULL) { - rv = -errno; - break; + if (strstr(bpath, "systemd")) { + rv = read_systemd_service(cfg, ipath, ipath_len, buf, size, offset); + } else { + rv = read_pass(cfg, ipath, ipath_len, buf, size, offset); } - size_t wrote = 0; - char line[PATH_MAX]; - if (offset < 0) { - rv = -EINVAL; - break; - } - size_t off = offset; - while (fgets(line, sizeof(line), fp) != NULL) { - int found = 0; - for (size_t i = 0; i < ARRAY_LEN(ini_inject_strat_str); - i++) { - if (strncmp(line, ini_inject_strat_str[i], - ini_inject_strat_len[i]) != 0) { - continue; - } - strcatoff(buf, ini_inject_strat_str[i], - ini_inject_strat_len[i], &off, &wrote, - size); - strcatoff(buf, STRAT_PATH, STRAT_PATH_LEN, &off, - &wrote, size); - strcatoff(buf, " ", 1, &off, &wrote, size); - strcatoff(buf, deref(back)->name, - deref(back)->name_len, &off, &wrote, - size); - strcatoff(buf, " ", 1, &off, &wrote, size); - strcatoff(buf, line + ini_inject_strat_len[i], - strlen(line + ini_inject_strat_len[i]), - &off, &wrote, size); - found = 1; - break; - } - for (size_t i = 0; i < ARRAY_LEN(ini_expand_path_str); - i++) { - if (strncmp(line, ini_expand_path_str[i], - ini_expand_path_len[i]) != 0 - || line[ini_expand_path_len[i]] != - '/') { - continue; - } - strcatoff(buf, ini_expand_path_str[i], - ini_expand_path_len[i], &off, &wrote, - size); - strcatoff(buf, STRATA_ROOT, STRATA_ROOT_LEN, - &off, &wrote, size); - strcatoff(buf, deref(back)->name, - deref(back)->name_len, &off, &wrote, - size); - strcatoff(buf, line + ini_expand_path_len[i], - strlen(line + ini_expand_path_len[i]), - &off, &wrote, size); - found = 1; - } - if (!found) { - strcatoff(buf, line, strlen(line), &off, - &wrote, size); - } - if (wrote >= size) { - break; - } - } - rv = wrote; - fclose(fp); + break; + + case FILTER_INI: + rv = inject_ini(cfg, ipath, ipath_len, buf, size, offset); break; case FILTER_FONT: @@ -2199,8 +2248,8 @@ static inline int read_back(struct cfg_entry *cfg, const char *ipath, size_t break; } - wrote = 0; - off = offset; + size_t wrote = 0; + size_t off = offset; /* * Handle line count line diff --git a/src/slash-bedrock/etc/bedrock.conf b/src/slash-bedrock/etc/bedrock.conf index 7be03034..0c664417 100644 --- a/src/slash-bedrock/etc/bedrock.conf +++ b/src/slash-bedrock/etc/bedrock.conf @@ -445,6 +445,9 @@ dbus-session = /usr/share/dbus-1/services # fonts = /usr/share/fonts +[cross-service] +usr/lib/systemd = /usr/lib/systemd + [pmm] # # Package Manager Manager diff --git a/src/slash-bedrock/share/common-code b/src/slash-bedrock/share/common-code index 6657e499..af6d55b9 100644 --- a/src/slash-bedrock/share/common-code +++ b/src/slash-bedrock/share/common-code @@ -1382,9 +1382,15 @@ cfg_crossfs() { continue } - target = filter" /"key" "n_strata[i]":"n_values[j] + if (filter == "service") { + # for services, use a different path + target = filter" /services/"n_strata[i]"/"key" "n_strata[i]":"n_values[j] + } else { + target = filter" /"key" "n_strata[i]":"n_values[j] + } + if (!(target in targets)) { - n_targets[++targets_len] = target + n_targets[++targets_len] = target targets[target] = target } } From 84c16f45dcab9f35a32e4c5ed591b0bff35876a3 Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Tue, 28 Jul 2020 23:07:39 +0300 Subject: [PATCH 2/4] Implement the base for init service translation. There is now a foundation for cross-stratum service translation. For now, just the most simple translation is implemented. That is, runit to systemd. Services are created on the fly and stored in memory cache, served from there when needed and updated if the original service file changes. As before, these need to be linked to the init stratum actual service directory to be used, but it is currently already possible to run the simples runit services with systemd. --- src/crossfs/crossfs.c | 310 +++++++++++++++++++++++++++++++----------- 1 file changed, 228 insertions(+), 82 deletions(-) diff --git a/src/crossfs/crossfs.c b/src/crossfs/crossfs.c index 0d52e13a..a99e6aee 100644 --- a/src/crossfs/crossfs.c +++ b/src/crossfs/crossfs.c @@ -71,6 +71,7 @@ #define FUSE_USE_VERSION 39 #define _GNU_SOURCE +#include #include #include #include @@ -286,6 +287,14 @@ enum filter { FILTER_PASS, }; +/* + * Type of init daemon. + */ +enum service_type { + SERVICE_TYPE_SYSTEMD, + SERVICE_TYPE_RUNIT +}; + const char *const filter_str[] = { "bin", "bin-restrict", @@ -422,6 +431,30 @@ struct h_kv { char key[]; }; +/* + * Hash table entry to hold generated services. + */ +struct h_generated_service { + UT_hash_handle hh; + char *service_text; + int service_text_len; + time_t modification_time; + char original_path[]; +}; + +/* + * An init-daemon-independant description of the service. + */ +struct service_desc { + char start[PATH_MAX]; + char stop[PATH_MAX]; +}; + +/* + * The hash table that holds generated services. + */ +static struct h_generated_service *generated_services = NULL; + /* * An array of cfg_entry's listing all of the user-facing files and directories * in this mount point. @@ -471,6 +504,12 @@ static struct stat cfg_stat; static struct stat local_stat; static off_t bouncer_size; +/* + * Init daemon type of the init stratum. Services from other strata + * will be translated to this type. + */ +static enum service_type init_stratum_service_type; + /* * Set the fsuid and fsgid to that of the calling function. setfsuid/setfsgid * do not indicate success/failure; we have to trust they succeed. A check @@ -1772,6 +1811,159 @@ static inline void getattr_ini(struct cfg_entry *cfg, const char *ipath, size_t fclose(fp); } +static inline int inject_ini(struct cfg_entry *cfg, const char *ipath, size_t ipath_len, char *buf, size_t size, off_t offset) { + struct back_entry *back; + char bpath[PATH_MAX]; + int rv = loc_first_bpath(cfg, ipath, ipath_len, &back, bpath); + if (rv < 0) { + return -errno; + } + + FILE *fp = fchroot_fopen_rdonly(deref(back)->root_fd, bpath); + if (fp == NULL) { + return -errno; + } + + size_t wrote = 0; + char line[PATH_MAX]; + if (offset < 0) { + return -EINVAL; + } + + size_t off = offset; + while (fgets(line, sizeof(line), fp) != NULL) { + int found = 0; + for (size_t i = 0; i < ARRAY_LEN(ini_inject_strat_str); + i++) { + if (strncmp(line, ini_inject_strat_str[i], + ini_inject_strat_len[i]) != 0) { + continue; + } + strcatoff(buf, ini_inject_strat_str[i], + ini_inject_strat_len[i], &off, &wrote, + size); + strcatoff(buf, STRAT_PATH, STRAT_PATH_LEN, &off, + &wrote, size); + strcatoff(buf, " ", 1, &off, &wrote, size); + strcatoff(buf, deref(back)->name, + deref(back)->name_len, &off, &wrote, + size); + strcatoff(buf, " ", 1, &off, &wrote, size); + strcatoff(buf, line + ini_inject_strat_len[i], + strlen(line + ini_inject_strat_len[i]), + &off, &wrote, size); + found = 1; + break; + } + for (size_t i = 0; i < ARRAY_LEN(ini_expand_path_str); + i++) { + if (strncmp(line, ini_expand_path_str[i], + ini_expand_path_len[i]) != 0 + || line[ini_expand_path_len[i]] != + '/') { + continue; + } + strcatoff(buf, ini_expand_path_str[i], + ini_expand_path_len[i], &off, &wrote, + size); + strcatoff(buf, STRATA_ROOT, STRATA_ROOT_LEN, + &off, &wrote, size); + strcatoff(buf, deref(back)->name, + deref(back)->name_len, &off, &wrote, + size); + strcatoff(buf, line + ini_expand_path_len[i], + strlen(line + ini_expand_path_len[i]), + &off, &wrote, size); + found = 1; + } + if (!found) { + strcatoff(buf, line, strlen(line), &off, + &wrote, size); + } + if (wrote >= size) { + break; + } + } + rv = wrote; + fclose(fp); + + return rv; +} + +static inline int generate_service_for(struct back_entry *back, char *bpath, enum service_type service_type, struct h_generated_service **generated_service) { + char *service_stratum = deref(back)->name; + + char full_service_path[PATH_MAX] = STRATA_ROOT; + strncat(full_service_path, service_stratum, PATH_MAX); + strncat(full_service_path, bpath, PATH_MAX); + + struct stat original_service_stat; + if (stat(full_service_path, &original_service_stat) != 0) { + return -errno; + } + + HASH_FIND_STR(generated_services, full_service_path, *generated_service); + + bool need_to_create = *generated_service == NULL; + + // If the modification date on the original service doesn't match the recorded one, + // the service should be regenerated + if (!need_to_create && (*generated_service)->modification_time != original_service_stat.st_mtime) { + // Remove the service from the table, free the service's text, and free the service itself + HASH_DEL(generated_services, *generated_service); + free((*generated_service)->service_text); + free(*generated_service); + + // Mark that a new service has to be generated + need_to_create = true; + } + + if (need_to_create) { + struct service_desc service_desc; + switch (service_type) { + case SERVICE_TYPE_RUNIT: + snprintf(service_desc.start, PATH_MAX, "/bedrock/bin/strat -r %s %s/run", service_stratum, full_service_path); + + break; + } + + // Allocate space for a generated service + service path data + *generated_service = malloc(sizeof(struct h_generated_service) + strlen(full_service_path) + 1); + if (*generated_service == NULL) + return -ENOMEM; + + strcpy((*generated_service)->original_path, full_service_path); + + switch (init_stratum_service_type) { + case SERVICE_TYPE_SYSTEMD: + (*generated_service)->service_text_len = asprintf(&(*generated_service)->service_text, "[Service]\nExecStart=%s\n", service_desc.start); + break; + } + + (*generated_service)->modification_time = original_service_stat.st_mtime; + HASH_ADD_STR(generated_services, original_path, *generated_service); + } +} + +static inline int read_service(struct cfg_entry *cfg, const char *const ipath, size_t ipath_len, + char *buf, size_t size, off_t offset, struct back_entry *back, char *bpath, + enum service_type service_type) { + if (init_stratum_service_type == service_type) { + switch (service_type) { + case SERVICE_TYPE_SYSTEMD: + return inject_ini(cfg, ipath, ipath_len, buf, size, offset); + break; + } + } else { + struct h_generated_service *generated_service = NULL; + generate_service_for(back, bpath, service_type, &generated_service); + + strncpy(buf, generated_service->service_text, generated_service->service_text_len); + + return generated_service->service_text_len; + } +} + static inline int getattr_back(struct cfg_entry *cfg, const char *ipath, size_t ipath_len, struct stat *stbuf) { @@ -1808,8 +2000,20 @@ static inline int getattr_back(struct cfg_entry *cfg, const char *ipath, break; } + const char *sv_dir = "/etc/sv/"; + const int sv_len = strlen(sv_dir); + if (strstr(bpath, "systemd")) { getattr_ini(cfg, ipath, ipath_len, stbuf, &rv); + } else if (is_parent(sv_dir, sv_len - 1, bpath, strlen(bpath)) != 0) { + // TODO: handle actual access mask here + stbuf->st_mode = S_IFREG | 0400; + stbuf->st_nlink = 1; + + struct h_generated_service *generated_service = NULL; + generate_service_for(back, bpath, SERVICE_TYPE_RUNIT, &generated_service); + + stbuf->st_size = generated_service->service_text_len; } break; @@ -2102,85 +2306,6 @@ static inline int read_pass(struct cfg_entry *cfg, const char *const ipath, return rv; } -static inline int inject_ini(struct cfg_entry *cfg, const char *ipath, size_t ipath_len, char *buf, size_t size, off_t offset) { - struct back_entry *back; - char bpath[PATH_MAX]; - int rv = loc_first_bpath(cfg, ipath, ipath_len, &back, bpath); - if (rv < 0) { - return -errno; - } - - FILE *fp = fchroot_fopen_rdonly(deref(back)->root_fd, bpath); - if (fp == NULL) { - return -errno; - } - - size_t wrote = 0; - char line[PATH_MAX]; - if (offset < 0) { - return -EINVAL; - } - - size_t off = offset; - while (fgets(line, sizeof(line), fp) != NULL) { - int found = 0; - for (size_t i = 0; i < ARRAY_LEN(ini_inject_strat_str); - i++) { - if (strncmp(line, ini_inject_strat_str[i], - ini_inject_strat_len[i]) != 0) { - continue; - } - strcatoff(buf, ini_inject_strat_str[i], - ini_inject_strat_len[i], &off, &wrote, - size); - strcatoff(buf, STRAT_PATH, STRAT_PATH_LEN, &off, - &wrote, size); - strcatoff(buf, " ", 1, &off, &wrote, size); - strcatoff(buf, deref(back)->name, - deref(back)->name_len, &off, &wrote, - size); - strcatoff(buf, " ", 1, &off, &wrote, size); - strcatoff(buf, line + ini_inject_strat_len[i], - strlen(line + ini_inject_strat_len[i]), - &off, &wrote, size); - found = 1; - break; - } - for (size_t i = 0; i < ARRAY_LEN(ini_expand_path_str); - i++) { - if (strncmp(line, ini_expand_path_str[i], - ini_expand_path_len[i]) != 0 - || line[ini_expand_path_len[i]] != - '/') { - continue; - } - strcatoff(buf, ini_expand_path_str[i], - ini_expand_path_len[i], &off, &wrote, - size); - strcatoff(buf, STRATA_ROOT, STRATA_ROOT_LEN, - &off, &wrote, size); - strcatoff(buf, deref(back)->name, - deref(back)->name_len, &off, &wrote, - size); - strcatoff(buf, line + ini_expand_path_len[i], - strlen(line + ini_expand_path_len[i]), - &off, &wrote, size); - found = 1; - } - if (!found) { - strcatoff(buf, line, strlen(line), &off, - &wrote, size); - } - if (wrote >= size) { - break; - } - } - rv = wrote; - fclose(fp); - - return rv; -} - static inline int read_systemd_service(struct cfg_entry *cfg, const char *const ipath, size_t ipath_len, char *buf, size_t size, off_t offset) { return inject_ini(cfg, ipath, ipath_len, buf, size, offset); } @@ -2206,11 +2331,21 @@ static inline int read_back(struct cfg_entry *cfg, const char *ipath, size_t break; } - if (strstr(bpath, "systemd")) { - rv = read_systemd_service(cfg, ipath, ipath_len, buf, size, offset); + const char *sv_dir = "/etc/sv/"; + const int sv_len = strlen(sv_dir); + + enum service_type service_type; + if (strstr(bpath, "systemd") != NULL) { + service_type = SERVICE_TYPE_SYSTEMD; + } else if (is_parent(sv_dir, sv_len - 1, bpath, strlen(bpath)) != 0) { + service_type = SERVICE_TYPE_RUNIT; } else { - rv = read_pass(cfg, ipath, ipath_len, buf, size, offset); + fprintf(stderr, "Unknown service type encountered"); + rv = -EBADF; + + break; } + rv = read_service(cfg, ipath, ipath_len, buf, size, offset, back, bpath, service_type); break; @@ -2540,6 +2675,17 @@ int main(int argc, char *argv[]) return 1; } + /* + * Determine the init daemon. + * TODO: this should probably set in the config instead + */ + struct stat init_check_stat; + if (stat("/lib/systemd/systemd", &init_check_stat) != -1) { + init_stratum_service_type = SERVICE_TYPE_SYSTEMD; + } else { + fprintf(stderr, "crossfs: Unable to determine the init system type\n"); + } + /* * Pre-calculate common stat() values. */ From 0b3f46cbe2859ef4a6d00e451af0cc566ea413ce Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Wed, 29 Jul 2020 21:13:54 +0300 Subject: [PATCH 3/4] Separate service gen and definitions. More generation from runit. systemd service generator now understands runit finish and conf. The general service model aquired a few new fields for the aforementioned things, plus a field for the healthchecking (which is not generated for systemd, because systemd can't do healthchecks with arbitary scripts easily). Overall, this should cover most of runit services. log from runit is left unimplemented, as other service daemons have their own means of logging. --- src/crossfs/Makefile | 2 +- src/crossfs/crossfs.c | 332 +------------------------------ src/crossfs/definitions.h | 215 ++++++++++++++++++++ src/crossfs/service_generation.h | 226 +++++++++++++++++++++ 4 files changed, 445 insertions(+), 330 deletions(-) create mode 100644 src/crossfs/definitions.h create mode 100644 src/crossfs/service_generation.h diff --git a/src/crossfs/Makefile b/src/crossfs/Makefile index 6080a0b3..bb28987c 100644 --- a/src/crossfs/Makefile +++ b/src/crossfs/Makefile @@ -8,7 +8,7 @@ all: crossfs -crossfs: crossfs.c +crossfs: crossfs.c definitions.h service_generation.h $(CC) $(CFLAGS) -std=c99 -D_FILE_OFFSET_BITS=64 crossfs.c -o crossfs -lfuse3 -lpthread clean: diff --git a/src/crossfs/crossfs.c b/src/crossfs/crossfs.c index a99e6aee..eab88d24 100644 --- a/src/crossfs/crossfs.c +++ b/src/crossfs/crossfs.c @@ -71,7 +71,6 @@ #define FUSE_USE_VERSION 39 #define _GNU_SOURCE -#include #include #include #include @@ -87,119 +86,12 @@ #include +#include "definitions.h" +#include "service_generation.h" + #define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0])) #define MIN(x, y) (x < y ? x : y) -/* - * The directory containing the roots of the various strata. References to a - * specific stratum's instance of a file go through this directory. - */ -#define STRATA_ROOT "/bedrock/strata/" -#define STRATA_ROOT_LEN strlen(STRATA_ROOT) - -/* - * Strat runs an executable from given stratum as specified in argument list. - * Crossfs injects this into various references to executables to ensure the - * appropriate stratum's instance of the executable is utilized in that - * context. This is useful for things such as .desktop files with `Exec=` - * references to executables. - */ -#define STRAT_PATH "/bedrock/bin/strat" -#define STRAT_PATH_LEN strlen(STRAT_PATH) - -/* - * Bouncer, like strat, redirects to the appropriate stratum's instance of an - * executable. It differs from strat in that it determines which executable by - * looking at its extended filesystem attributes rather than its arguments. - * This is useful for binaries the user will directly execute, as the user - * controls the argument list. - */ -#define BOUNCER_PATH "/bedrock/libexec/bouncer" -#define BOUNCER_PATH_LEN strlen(BOUNCER_PATH) - -/* - * The root of the procfs filesystem - */ -#define PROCFS_ROOT "/proc" - -/* - * Surface the associated stratum and file path for files via xattrs. - */ -#define STRATUM_XATTR "user.bedrock.stratum" -#define STRATUM_XATTR_LEN strlen(STRATUM_XATTR) - -#define LPATH_XATTR "user.bedrock.localpath" -#define LPATH_XATTR_LEN strlen(LPATH_XATTR) - -#define RESTRICT_XATTR "user.bedrock.restrict" -#define RESTRICT_XATTR_LEN strlen(RESTRICT_XATTR) -#define RESTRICT "restrict" -#define RESTRICT_LEN strlen(RESTRICT) - -/* - * Crossfs may be configured to present a file without being explicitly - * configured to also present its parent directory. It will dynamically create - * a virtual directory in these cases. - * - * This filesystem is typically mounted in the bedrock stratum then shared with - * other strata as a global path. Thus, by definition, anything that isn't - * crossed to another stratum is owned by the bedrock stratum, including these - * virtual directories. - */ -#define VIRTUAL_STRATUM "bedrock" -#define VIRTUAL_STRATUM_LEN strlen(VIRTUAL_STRATUM) -/* - * All crossfs files have an associated file path. While "/" isn't - * particularly meaningful here, no other path is obviously better. - */ -#define VIRTUAL_LPATH "/" -#define VIRTUAL_LPATH_LEN strlen(VIRTUAL_LPATH) - -/* - * When merging font directories, these files require extra attention. - */ -#define FONTS_DIR "fonts.dir" -#define FONTS_DIR_LEN strlen(FONTS_DIR) - -#define FONTS_ALIAS "fonts.alias" -#define FONTS_ALIAS_LEN strlen(FONTS_ALIAS) - -/* - * The file path used to configure this filesystem. - */ -#define CFG_NAME ".bedrock-config-filesystem" -#define CFG_NAME_LEN strlen(CFG_NAME) - -#define CFG_PATH "/.bedrock-config-filesystem" -#define CFG_PATH_LEN strlen(CFG_PATH) - -/* - * Symlink to stratum root, used for local alias. - */ -#define LOCAL_ALIAS_NAME ".local-alias" -#define LOCAL_ALIAS_NAME_LEN strlen(LOCAL_ALIAS_NAME) - -#define LOCAL_ALIAS_PATH "/.local-alias" -#define LOCAL_ALIAS_PATH_LEN strlen(LOCAL_ALIAS_PATH) - -/* - * local alias - */ -#define LOCAL "local" -#define LOCAL_LEN strlen(LOCAL) - -/* - * Headers for content written to CFG_NAME - */ -#define CMD_CLEAR "clear" -#define CMD_CLEAR_LEN strlen(CMD_CLEAR) - -#define CMD_ADD "add" -#define CMD_ADD_LEN strlen(CMD_ADD) - -#define CMD_RM "rm" -#define CMD_RM_LEN strlen(CMD_RM) - #define FS_IMP_SETUP(lock_type) \ int rv; \ set_caller_fsid(); \ @@ -255,55 +147,6 @@ enum ipath_class { CLASS_ENOENT, }; -/* - * This filesystem may modify contents as it passes the backing file to the - * requesting process. The filter indicates the scheme used to modify the - * contents. - */ -enum filter { - /* - * Files are expected to be executables. Return bouncer. - */ - FILTER_BIN, - /* - * Files are expected to be executables. Return bouncer with restrict set. - */ - FILTER_BIN_RESTRICT, - /* - * Files are expected to be in ini-format. Performs various - * transformations such as injecting calls to strat or stratum root - * paths. - */ - FILTER_INI, - /* - * Combine fonts.dir and fonts.aliases files. - */ - FILTER_FONT, - - FILTER_SERVICE, - /* - * Pass file through unaltered. - */ - FILTER_PASS, -}; - -/* - * Type of init daemon. - */ -enum service_type { - SERVICE_TYPE_SYSTEMD, - SERVICE_TYPE_RUNIT -}; - -const char *const filter_str[] = { - "bin", - "bin-restrict", - "ini", - "font", - "service", - "pass", -}; - /* * Wrap ini values with strat calls. * @@ -353,67 +196,6 @@ const size_t ini_expand_path_len[] = { 8, }; -struct stratum { - /* - * stratum name - */ - char *name; - size_t name_len; - /* - * A file descriptor relating to the corresponding stratum's root - * directory. - */ - int root_fd; -}; - -/* - * Each back_entry represents a file or directory which may fulfill a given - * cfg_entry file. - */ -struct back_entry { - /* - * The stratum-local path. - */ - char *lpath; - size_t lpath_len; - /* - * The corresponding stratum/alias. Run through deref() before - * consumption. - */ - struct stratum alias; - /* - * Boolean indicating if this back_entry uses the local alias. If so, - * deref() forwards calls to the local stratum rather than this - * struct's alias field. - */ - int local; -}; - -/* - * Each cfg_entry represents a user-facing file or directory in the mount - * point. - */ -struct cfg_entry { - /* - * Filter to apply to output. - */ - enum filter filter; - /* - * Path to append to mount point's path. For example, if this - * filesystem is mounted at "/bedrock/cross" and path="/man", this - * cfg_entry refers to "/bedrock/cross/man". Note the preceding slash. - */ - char *cpath; - size_t cpath_len; - /* - * Array of filesystem paths to be searched for this cfg_entry's - * backing file(s). - */ - struct back_entry *back; - size_t back_cnt; - size_t back_alloc; -}; - /* * Hash table entry for a single string. */ @@ -431,30 +213,6 @@ struct h_kv { char key[]; }; -/* - * Hash table entry to hold generated services. - */ -struct h_generated_service { - UT_hash_handle hh; - char *service_text; - int service_text_len; - time_t modification_time; - char original_path[]; -}; - -/* - * An init-daemon-independant description of the service. - */ -struct service_desc { - char start[PATH_MAX]; - char stop[PATH_MAX]; -}; - -/* - * The hash table that holds generated services. - */ -static struct h_generated_service *generated_services = NULL; - /* * An array of cfg_entry's listing all of the user-facing files and directories * in this mount point. @@ -504,12 +262,6 @@ static struct stat cfg_stat; static struct stat local_stat; static off_t bouncer_size; -/* - * Init daemon type of the init stratum. Services from other strata - * will be translated to this type. - */ -static enum service_type init_stratum_service_type; - /* * Set the fsuid and fsgid to that of the calling function. setfsuid/setfsgid * do not indicate success/failure; we have to trust they succeed. A check @@ -1890,80 +1642,6 @@ static inline int inject_ini(struct cfg_entry *cfg, const char *ipath, size_t ip return rv; } -static inline int generate_service_for(struct back_entry *back, char *bpath, enum service_type service_type, struct h_generated_service **generated_service) { - char *service_stratum = deref(back)->name; - - char full_service_path[PATH_MAX] = STRATA_ROOT; - strncat(full_service_path, service_stratum, PATH_MAX); - strncat(full_service_path, bpath, PATH_MAX); - - struct stat original_service_stat; - if (stat(full_service_path, &original_service_stat) != 0) { - return -errno; - } - - HASH_FIND_STR(generated_services, full_service_path, *generated_service); - - bool need_to_create = *generated_service == NULL; - - // If the modification date on the original service doesn't match the recorded one, - // the service should be regenerated - if (!need_to_create && (*generated_service)->modification_time != original_service_stat.st_mtime) { - // Remove the service from the table, free the service's text, and free the service itself - HASH_DEL(generated_services, *generated_service); - free((*generated_service)->service_text); - free(*generated_service); - - // Mark that a new service has to be generated - need_to_create = true; - } - - if (need_to_create) { - struct service_desc service_desc; - switch (service_type) { - case SERVICE_TYPE_RUNIT: - snprintf(service_desc.start, PATH_MAX, "/bedrock/bin/strat -r %s %s/run", service_stratum, full_service_path); - - break; - } - - // Allocate space for a generated service + service path data - *generated_service = malloc(sizeof(struct h_generated_service) + strlen(full_service_path) + 1); - if (*generated_service == NULL) - return -ENOMEM; - - strcpy((*generated_service)->original_path, full_service_path); - - switch (init_stratum_service_type) { - case SERVICE_TYPE_SYSTEMD: - (*generated_service)->service_text_len = asprintf(&(*generated_service)->service_text, "[Service]\nExecStart=%s\n", service_desc.start); - break; - } - - (*generated_service)->modification_time = original_service_stat.st_mtime; - HASH_ADD_STR(generated_services, original_path, *generated_service); - } -} - -static inline int read_service(struct cfg_entry *cfg, const char *const ipath, size_t ipath_len, - char *buf, size_t size, off_t offset, struct back_entry *back, char *bpath, - enum service_type service_type) { - if (init_stratum_service_type == service_type) { - switch (service_type) { - case SERVICE_TYPE_SYSTEMD: - return inject_ini(cfg, ipath, ipath_len, buf, size, offset); - break; - } - } else { - struct h_generated_service *generated_service = NULL; - generate_service_for(back, bpath, service_type, &generated_service); - - strncpy(buf, generated_service->service_text, generated_service->service_text_len); - - return generated_service->service_text_len; - } -} - static inline int getattr_back(struct cfg_entry *cfg, const char *ipath, size_t ipath_len, struct stat *stbuf) { @@ -2306,10 +1984,6 @@ static inline int read_pass(struct cfg_entry *cfg, const char *const ipath, return rv; } -static inline int read_systemd_service(struct cfg_entry *cfg, const char *const ipath, size_t ipath_len, char *buf, size_t size, off_t offset) { - return inject_ini(cfg, ipath, ipath_len, buf, size, offset); -} - static inline int read_back(struct cfg_entry *cfg, const char *ipath, size_t ipath_len, char *buf, size_t size, off_t offset) { diff --git a/src/crossfs/definitions.h b/src/crossfs/definitions.h new file mode 100644 index 00000000..b29adc25 --- /dev/null +++ b/src/crossfs/definitions.h @@ -0,0 +1,215 @@ +#pragma once + +#include + +/* + * The directory containing the roots of the various strata. References to a + * specific stratum's instance of a file go through this directory. + */ +#define STRATA_ROOT "/bedrock/strata/" +#define STRATA_ROOT_LEN strlen(STRATA_ROOT) + +/* + * Strat runs an executable from given stratum as specified in argument list. + * Crossfs injects this into various references to executables to ensure the + * appropriate stratum's instance of the executable is utilized in that + * context. This is useful for things such as .desktop files with `Exec=` + * references to executables. + */ +#define STRAT_PATH "/bedrock/bin/strat" +#define STRAT_PATH_LEN strlen(STRAT_PATH) + +/* + * Bouncer, like strat, redirects to the appropriate stratum's instance of an + * executable. It differs from strat in that it determines which executable by + * looking at its extended filesystem attributes rather than its arguments. + * This is useful for binaries the user will directly execute, as the user + * controls the argument list. + */ +#define BOUNCER_PATH "/bedrock/libexec/bouncer" +#define BOUNCER_PATH_LEN strlen(BOUNCER_PATH) + +/* + * The root of the procfs filesystem + */ +#define PROCFS_ROOT "/proc" + +/* + * Surface the associated stratum and file path for files via xattrs. + */ +#define STRATUM_XATTR "user.bedrock.stratum" +#define STRATUM_XATTR_LEN strlen(STRATUM_XATTR) + +#define LPATH_XATTR "user.bedrock.localpath" +#define LPATH_XATTR_LEN strlen(LPATH_XATTR) + +#define RESTRICT_XATTR "user.bedrock.restrict" +#define RESTRICT_XATTR_LEN strlen(RESTRICT_XATTR) +#define RESTRICT "restrict" +#define RESTRICT_LEN strlen(RESTRICT) + +/* + * Crossfs may be configured to present a file without being explicitly + * configured to also present its parent directory. It will dynamically create + * a virtual directory in these cases. + * + * This filesystem is typically mounted in the bedrock stratum then shared with + * other strata as a global path. Thus, by definition, anything that isn't + * crossed to another stratum is owned by the bedrock stratum, including these + * virtual directories. + */ +#define VIRTUAL_STRATUM "bedrock" +#define VIRTUAL_STRATUM_LEN strlen(VIRTUAL_STRATUM) +/* + * All crossfs files have an associated file path. While "/" isn't + * particularly meaningful here, no other path is obviously better. + */ +#define VIRTUAL_LPATH "/" +#define VIRTUAL_LPATH_LEN strlen(VIRTUAL_LPATH) + +/* + * When merging font directories, these files require extra attention. + */ +#define FONTS_DIR "fonts.dir" +#define FONTS_DIR_LEN strlen(FONTS_DIR) + +#define FONTS_ALIAS "fonts.alias" +#define FONTS_ALIAS_LEN strlen(FONTS_ALIAS) + +/* + * The file path used to configure this filesystem. + */ +#define CFG_NAME ".bedrock-config-filesystem" +#define CFG_NAME_LEN strlen(CFG_NAME) + +#define CFG_PATH "/.bedrock-config-filesystem" +#define CFG_PATH_LEN strlen(CFG_PATH) + +/* + * Symlink to stratum root, used for local alias. + */ +#define LOCAL_ALIAS_NAME ".local-alias" +#define LOCAL_ALIAS_NAME_LEN strlen(LOCAL_ALIAS_NAME) + +#define LOCAL_ALIAS_PATH "/.local-alias" +#define LOCAL_ALIAS_PATH_LEN strlen(LOCAL_ALIAS_PATH) + +/* + * local alias + */ +#define LOCAL "local" +#define LOCAL_LEN strlen(LOCAL) + +/* + * Headers for content written to CFG_NAME + */ +#define CMD_CLEAR "clear" +#define CMD_CLEAR_LEN strlen(CMD_CLEAR) + +#define CMD_ADD "add" +#define CMD_ADD_LEN strlen(CMD_ADD) + +#define CMD_RM "rm" +#define CMD_RM_LEN strlen(CMD_RM) + +/* + * This filesystem may modify contents as it passes the backing file to the + * requesting process. The filter indicates the scheme used to modify the + * contents. + */ +enum filter { + /* + * Files are expected to be executables. Return bouncer. + */ + FILTER_BIN, + /* + * Files are expected to be executables. Return bouncer with restrict set. + */ + FILTER_BIN_RESTRICT, + /* + * Files are expected to be in ini-format. Performs various + * transformations such as injecting calls to strat or stratum root + * paths. + */ + FILTER_INI, + /* + * Combine fonts.dir and fonts.aliases files. + */ + FILTER_FONT, + + FILTER_SERVICE, + /* + * Pass file through unaltered. + */ + FILTER_PASS, +}; + +const char *const filter_str[] = { + "bin", + "bin-restrict", + "ini", + "font", + "service", + "pass", +}; + +struct stratum { + /* + * stratum name + */ + char *name; + size_t name_len; + /* + * A file descriptor relating to the corresponding stratum's root + * directory. + */ + int root_fd; +}; + +/* + * Each back_entry represents a file or directory which may fulfill a given + * cfg_entry file. + */ +struct back_entry { + /* + * The stratum-local path. + */ + char *lpath; + size_t lpath_len; + /* + * The corresponding stratum/alias. Run through deref() before + * consumption. + */ + struct stratum alias; + /* + * Boolean indicating if this back_entry uses the local alias. If so, + * deref() forwards calls to the local stratum rather than this + * struct's alias field. + */ + int local; +}; + +/* + * Each cfg_entry represents a user-facing file or directory in the mount + * point. + */ +struct cfg_entry { + /* + * Filter to apply to output. + */ + enum filter filter; + /* + * Path to append to mount point's path. For example, if this + * filesystem is mounted at "/bedrock/cross" and path="/man", this + * cfg_entry refers to "/bedrock/cross/man". Note the preceding slash. + */ + char *cpath; + size_t cpath_len; + /* + * Array of filesystem paths to be searched for this cfg_entry's + * backing file(s). + */ + struct back_entry *back; + size_t back_cnt; + size_t back_alloc; +}; diff --git a/src/crossfs/service_generation.h b/src/crossfs/service_generation.h new file mode 100644 index 00000000..a95c511a --- /dev/null +++ b/src/crossfs/service_generation.h @@ -0,0 +1,226 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include "definitions.h" + +/* + * Hash table entry to hold generated services. + */ +struct h_generated_service { + UT_hash_handle hh; + char *service_text; + int service_text_len; + time_t modification_time; + char original_path[]; +}; + +/* + * An init-daemon-independant description of the service. + */ +struct service_desc { + /* + * A command to start the service. + */ + char start[PATH_MAX]; + size_t start_len; + + /* + * An optional command to stop the service. + */ + char stop[PATH_MAX]; + size_t stop_len; + + /* + * An optional command to run after the service has stopped. + */ + char after_stop[PATH_MAX]; + size_t after_stop_len; + + /* + * An optional path to the file which contains configuration / environment variables. + */ + char conf[PATH_MAX]; + size_t conf_len; + + /* + * An optional command to run as healthcheck. + */ + char check[PATH_MAX]; + size_t check_len; +}; + +/* + * Type of init daemon. + */ +enum service_type { + SERVICE_TYPE_SYSTEMD, + SERVICE_TYPE_RUNIT +}; + +/* + * Init daemon type of the init stratum. Services from other strata + * will be translated to this type. + */ +static enum service_type init_stratum_service_type; + +/* + * The hash table that holds generated services. + */ +static struct h_generated_service *generated_services = NULL; + +static inline struct stratum *deref(struct back_entry *back); +static inline int inject_ini(struct cfg_entry *cfg, const char *ipath, size_t ipath_len, char *buf, size_t size, off_t offset); + +static inline int generate_systemd_service(struct service_desc *desc, struct h_generated_service *generated_service) { + generated_service->service_text_len = asprintf( + &generated_service->service_text, + "[Unit]\nDescription=This service was generated by bedrock from %s\n\n" + "[Service]\nExecStart=%s\n", + generated_service->original_path, + desc->start + ); + if (generated_service->service_text_len < 0) + return generated_service->service_text_len; + + if (desc->after_stop_len > 0) { + generated_service->service_text_len = asprintf( + &generated_service->service_text, + "%sExecStopPost=%s\n", + generated_service->service_text, + desc->after_stop + ); + if (generated_service->service_text_len < 0) + return generated_service->service_text_len; + } + if (desc->conf_len) { + generated_service->service_text_len = asprintf( + &generated_service->service_text, + "%sEnvironmentFile=%s\n", + generated_service->service_text, + desc->conf + ); + if (generated_service->service_text_len < 0) + return generated_service->service_text_len; + } + + // systemd doesn't know how to do healthchecks with arbitary scripts + + return 0; +} + +static inline int generate_service_for(struct back_entry *back, char *bpath, enum service_type service_type, struct h_generated_service **generated_service) { + char *service_stratum = deref(back)->name; + + char full_service_path[PATH_MAX] = STRATA_ROOT; + strncat(full_service_path, service_stratum, PATH_MAX); + strncat(full_service_path, bpath, PATH_MAX); + + struct stat original_service_stat; + if (stat(full_service_path, &original_service_stat) != 0) { + return -errno; + } + + HASH_FIND_STR(generated_services, full_service_path, *generated_service); + + bool need_to_create = *generated_service == NULL; + + // If the modification date on the original service doesn't match the recorded one, + // the service should be regenerated + if (!need_to_create && (*generated_service)->modification_time != original_service_stat.st_mtime) { + // Remove the service from the table, free the service's text, and free the service itself + HASH_DEL(generated_services, *generated_service); + free((*generated_service)->service_text); + free(*generated_service); + + // Mark that a new service has to be generated + need_to_create = true; + } + + if (need_to_create) { + struct service_desc service_desc = { .start_len = 0, .stop_len = 0, .conf_len = 0 }; + + switch (service_type) { + case SERVICE_TYPE_RUNIT: + service_desc.start_len = + snprintf(service_desc.start, PATH_MAX, "/bedrock/bin/strat -r %s %s/run", service_stratum, full_service_path); + + // If there's a finish file, record to execute it after the service has stopped + char after_stop_file_path[PATH_MAX]; + strncpy(after_stop_file_path, full_service_path, PATH_MAX); + strcat(after_stop_file_path, "/finish"); + if (access(after_stop_file_path, F_OK) == 0) { + service_desc.after_stop_len = snprintf( + service_desc.after_stop, PATH_MAX, "/bedrock/bin/strat -r %s %s", + service_stratum, after_stop_file_path + ); + } + + char conf_file_path[PATH_MAX]; + strncpy(conf_file_path, full_service_path, PATH_MAX); + strcat(conf_file_path, "/conf"); + if (access(conf_file_path, F_OK) == 0) { + service_desc.conf_len = strlen(conf_file_path); + strncpy(service_desc.conf, conf_file_path, service_desc.conf_len); + } + + char check_file_path[PATH_MAX]; + strncpy(check_file_path, full_service_path, PATH_MAX); + strcat(check_file_path, "/check"); + if (access(check_file_path, F_OK) == 0) { + service_desc.check_len = snprintf( + service_desc.check, PATH_MAX, "/bedrock/bin/strat -r %s %s", + service_stratum, check_file_path + ); + } + + break; + } + + // Allocate space for a generated service + service path data + *generated_service = malloc(sizeof(struct h_generated_service) + strlen(full_service_path) + 1); + if (*generated_service == NULL) + return -ENOMEM; + + strcpy((*generated_service)->original_path, full_service_path); + + switch (init_stratum_service_type) { + case SERVICE_TYPE_SYSTEMD: + ; + int rv = generate_systemd_service(&service_desc, *generated_service); + if (rv < 0) + return rv; + + break; + } + + (*generated_service)->modification_time = original_service_stat.st_mtime; + HASH_ADD_STR(generated_services, original_path, *generated_service); + } +} + +static inline int read_service(struct cfg_entry *cfg, const char *const ipath, size_t ipath_len, + char *buf, size_t size, off_t offset, struct back_entry *back, char *bpath, + enum service_type service_type) { + if (init_stratum_service_type == service_type) { + switch (service_type) { + case SERVICE_TYPE_SYSTEMD: + return inject_ini(cfg, ipath, ipath_len, buf, size, offset); + break; + } + } else { + struct h_generated_service *generated_service = NULL; + generate_service_for(back, bpath, service_type, &generated_service); + + strncpy(buf, generated_service->service_text, generated_service->service_text_len); + + return generated_service->service_text_len; + } +} From 063a99810e22d1066fabd6fe9519bc087e65d6cb Mon Sep 17 00:00:00 2001 From: Ekaterina Vaartis Date: Wed, 5 Aug 2020 19:26:59 +0300 Subject: [PATCH 4/4] Initial OpenRC service generation implementation OpenRC is a bit tricky.. it's more complex than sv and it seems to be easier to just use openrc-run directly, however for that to work softlevel file has to exist and the openrc command has to be run at least once so it could create /var/run/openrc stuff. Otherwise, setting a specific PIDFile for systemd and starting openrc-run seems to work. --- src/crossfs/crossfs.c | 17 ++- src/crossfs/service_generation.h | 193 +++++++++++++++++------------ src/slash-bedrock/etc/bedrock.conf | 2 + 3 files changed, 132 insertions(+), 80 deletions(-) diff --git a/src/crossfs/crossfs.c b/src/crossfs/crossfs.c index eab88d24..d6543624 100644 --- a/src/crossfs/crossfs.c +++ b/src/crossfs/crossfs.c @@ -1681,6 +1681,9 @@ static inline int getattr_back(struct cfg_entry *cfg, const char *ipath, const char *sv_dir = "/etc/sv/"; const int sv_len = strlen(sv_dir); + const char *init_d = "/etc/init.d/"; + const int init_d_len = strlen(init_d); + if (strstr(bpath, "systemd")) { getattr_ini(cfg, ipath, ipath_len, stbuf, &rv); } else if (is_parent(sv_dir, sv_len - 1, bpath, strlen(bpath)) != 0) { @@ -1688,9 +1691,14 @@ static inline int getattr_back(struct cfg_entry *cfg, const char *ipath, stbuf->st_mode = S_IFREG | 0400; stbuf->st_nlink = 1; - struct h_generated_service *generated_service = NULL; + struct generated_service *generated_service = NULL; generate_service_for(back, bpath, SERVICE_TYPE_RUNIT, &generated_service); + stbuf->st_size = generated_service->service_text_len; + } else if (is_parent(init_d, init_d_len - 1, bpath, strlen(bpath)) != 0) { + struct generated_service *generated_service = NULL; + generate_service_for(back, bpath, SERVICE_TYPE_OPENRC, &generated_service); + stbuf->st_size = generated_service->service_text_len; } @@ -2008,11 +2016,16 @@ static inline int read_back(struct cfg_entry *cfg, const char *ipath, size_t const char *sv_dir = "/etc/sv/"; const int sv_len = strlen(sv_dir); + const char *init_d = "/etc/init.d/"; + const int init_d_len = strlen(init_d); + enum service_type service_type; if (strstr(bpath, "systemd") != NULL) { service_type = SERVICE_TYPE_SYSTEMD; } else if (is_parent(sv_dir, sv_len - 1, bpath, strlen(bpath)) != 0) { service_type = SERVICE_TYPE_RUNIT; + }else if (is_parent(init_d, init_d_len - 1, bpath, strlen(bpath)) != 0) { + service_type = SERVICE_TYPE_OPENRC; } else { fprintf(stderr, "Unknown service type encountered"); rv = -EBADF; @@ -2356,6 +2369,8 @@ int main(int argc, char *argv[]) struct stat init_check_stat; if (stat("/lib/systemd/systemd", &init_check_stat) != -1) { init_stratum_service_type = SERVICE_TYPE_SYSTEMD; + } else if (stat("/var/run/openrc", &init_check_stat) != -1) { + init_stratum_service_type = SERVICE_TYPE_OPENRC; } else { fprintf(stderr, "crossfs: Unable to determine the init system type\n"); } diff --git a/src/crossfs/service_generation.h b/src/crossfs/service_generation.h index a95c511a..3e11fcf8 100644 --- a/src/crossfs/service_generation.h +++ b/src/crossfs/service_generation.h @@ -6,19 +6,18 @@ #include #include #include +#include #include #include "definitions.h" /* - * Hash table entry to hold generated services. + * A generated service. */ -struct h_generated_service { - UT_hash_handle hh; +struct generated_service { char *service_text; int service_text_len; - time_t modification_time; char original_path[]; }; @@ -55,6 +54,9 @@ struct service_desc { */ char check[PATH_MAX]; size_t check_len; + + char pidfile[PATH_MAX]; + size_t pidfile_len; }; /* @@ -62,7 +64,8 @@ struct service_desc { */ enum service_type { SERVICE_TYPE_SYSTEMD, - SERVICE_TYPE_RUNIT + SERVICE_TYPE_RUNIT, + SERVICE_TYPE_OPENRC }; /* @@ -71,18 +74,13 @@ enum service_type { */ static enum service_type init_stratum_service_type; -/* - * The hash table that holds generated services. - */ -static struct h_generated_service *generated_services = NULL; - static inline struct stratum *deref(struct back_entry *back); static inline int inject_ini(struct cfg_entry *cfg, const char *ipath, size_t ipath_len, char *buf, size_t size, off_t offset); -static inline int generate_systemd_service(struct service_desc *desc, struct h_generated_service *generated_service) { +static inline int generate_systemd_service(struct service_desc *desc, struct generated_service *generated_service) { generated_service->service_text_len = asprintf( &generated_service->service_text, - "[Unit]\nDescription=This service was generated by bedrock from %s\n\n" + "[Unit]\nDescription=Service generated by bedrock from %s\n\n" "[Service]\nExecStart=%s\n", generated_service->original_path, desc->start @@ -90,6 +88,17 @@ static inline int generate_systemd_service(struct service_desc *desc, struct h_g if (generated_service->service_text_len < 0) return generated_service->service_text_len; + if (desc->stop_len > 0) { + generated_service->service_text_len = asprintf( + &generated_service->service_text, + "%sExecStop=%s\n", + generated_service->service_text, + desc->stop + ); + if (generated_service->service_text_len < 0) + return generated_service->service_text_len; + } + if (desc->after_stop_len > 0) { generated_service->service_text_len = asprintf( &generated_service->service_text, @@ -111,12 +120,23 @@ static inline int generate_systemd_service(struct service_desc *desc, struct h_g return generated_service->service_text_len; } + if (desc->pidfile_len > 0) { + generated_service->service_text_len = asprintf( + &generated_service->service_text, + "%sPIDFile=%s\n", + generated_service->service_text, + desc->pidfile + ); + if (generated_service->service_text_len < 0) + return generated_service->service_text_len; + } + // systemd doesn't know how to do healthchecks with arbitary scripts return 0; } -static inline int generate_service_for(struct back_entry *back, char *bpath, enum service_type service_type, struct h_generated_service **generated_service) { +static inline int generate_service_for(struct back_entry *back, char *bpath, enum service_type service_type, struct generated_service **generated_service) { char *service_stratum = deref(back)->name; char full_service_path[PATH_MAX] = STRATA_ROOT; @@ -128,81 +148,96 @@ static inline int generate_service_for(struct back_entry *back, char *bpath, enu return -errno; } - HASH_FIND_STR(generated_services, full_service_path, *generated_service); - bool need_to_create = *generated_service == NULL; + struct service_desc service_desc = { .start_len = 0, .stop_len = 0, .conf_len = 0 }; - // If the modification date on the original service doesn't match the recorded one, - // the service should be regenerated - if (!need_to_create && (*generated_service)->modification_time != original_service_stat.st_mtime) { - // Remove the service from the table, free the service's text, and free the service itself - HASH_DEL(generated_services, *generated_service); - free((*generated_service)->service_text); - free(*generated_service); + switch (service_type) { + case SERVICE_TYPE_RUNIT: { + service_desc.start_len = + snprintf(service_desc.start, PATH_MAX, "/bedrock/bin/strat -r %s %s/run", service_stratum, full_service_path); + + // If there's a finish file, record to execute it after the service has stopped + char after_stop_file_path[PATH_MAX]; + strncpy(after_stop_file_path, full_service_path, PATH_MAX); + strcat(after_stop_file_path, "/finish"); + if (access(after_stop_file_path, F_OK) == 0) { + service_desc.after_stop_len = snprintf( + service_desc.after_stop, PATH_MAX, "/bedrock/bin/strat -r %s %s", + service_stratum, after_stop_file_path + ); + } - // Mark that a new service has to be generated - need_to_create = true; - } + char conf_file_path[PATH_MAX]; + strncpy(conf_file_path, full_service_path, PATH_MAX); + strcat(conf_file_path, "/conf"); + if (access(conf_file_path, F_OK) == 0) { + service_desc.conf_len = strlen(conf_file_path); + strncpy(service_desc.conf, conf_file_path, service_desc.conf_len); + } - if (need_to_create) { - struct service_desc service_desc = { .start_len = 0, .stop_len = 0, .conf_len = 0 }; - - switch (service_type) { - case SERVICE_TYPE_RUNIT: - service_desc.start_len = - snprintf(service_desc.start, PATH_MAX, "/bedrock/bin/strat -r %s %s/run", service_stratum, full_service_path); - - // If there's a finish file, record to execute it after the service has stopped - char after_stop_file_path[PATH_MAX]; - strncpy(after_stop_file_path, full_service_path, PATH_MAX); - strcat(after_stop_file_path, "/finish"); - if (access(after_stop_file_path, F_OK) == 0) { - service_desc.after_stop_len = snprintf( - service_desc.after_stop, PATH_MAX, "/bedrock/bin/strat -r %s %s", - service_stratum, after_stop_file_path - ); - } - - char conf_file_path[PATH_MAX]; - strncpy(conf_file_path, full_service_path, PATH_MAX); - strcat(conf_file_path, "/conf"); - if (access(conf_file_path, F_OK) == 0) { - service_desc.conf_len = strlen(conf_file_path); - strncpy(service_desc.conf, conf_file_path, service_desc.conf_len); - } - - char check_file_path[PATH_MAX]; - strncpy(check_file_path, full_service_path, PATH_MAX); - strcat(check_file_path, "/check"); - if (access(check_file_path, F_OK) == 0) { - service_desc.check_len = snprintf( - service_desc.check, PATH_MAX, "/bedrock/bin/strat -r %s %s", - service_stratum, check_file_path - ); - } - - break; + char check_file_path[PATH_MAX]; + strncpy(check_file_path, full_service_path, PATH_MAX); + strcat(check_file_path, "/check"); + if (access(check_file_path, F_OK) == 0) { + service_desc.check_len = snprintf( + service_desc.check, PATH_MAX, "/bedrock/bin/strat -r %s %s", + service_stratum, check_file_path + ); } - // Allocate space for a generated service + service path data - *generated_service = malloc(sizeof(struct h_generated_service) + strlen(full_service_path) + 1); - if (*generated_service == NULL) - return -ENOMEM; + break; + } + case SERVICE_TYPE_OPENRC: { + char command_base[PATH_MAX]; + snprintf(command_base, + PATH_MAX, + "/bedrock/bin/strat -r %s " STRATA_ROOT "%s/sbin/openrc-run %s", + service_stratum, service_stratum, full_service_path + ); + + service_desc.start_len = + snprintf(service_desc.start, PATH_MAX, "%s start", command_base); + service_desc.stop_len = + snprintf(service_desc.stop, PATH_MAX, "%s stop", command_base); + + char *service_name = basename(bpath); + + char conf_file_path[PATH_MAX]; + size_t conf_file_len = + snprintf(conf_file_path, PATH_MAX, STRATA_ROOT "%s/etc/conf.d/%s", service_stratum, service_name); + if (access(conf_file_path, F_OK) == 0) { + service_desc.conf_len = conf_file_len; + strncpy(service_desc.conf, conf_file_path, conf_file_len); + } - strcpy((*generated_service)->original_path, full_service_path); + // This only work with daemons started via start-stop-daemon and without custom PID files. + // Generally, OpenRC write a file from which PIDFile can be determined after service start, + // but only after service start.. Otherwise, the pattern seems to be /run/supervise-name.pid + service_desc.pidfile_len = snprintf(service_desc.pidfile, PATH_MAX, "/run/supervise-%s.pid", service_name); - switch (init_stratum_service_type) { - case SERVICE_TYPE_SYSTEMD: - ; - int rv = generate_systemd_service(&service_desc, *generated_service); - if (rv < 0) - return rv; + // openrc-run refuses to run if this file doesn't exist + char softlevel_path[PATH_MAX]; + snprintf(softlevel_path, PATH_MAX, STRATA_ROOT "%s/run/openrc/softlevel", service_stratum); + if (!access(softlevel_path, F_OK)) + fclose(fopen(softlevel_path, "w")); + } + } - break; - } + // Allocate space for a generated service + service path data + *generated_service = malloc(sizeof(struct generated_service) + strlen(full_service_path) + 1); + if (*generated_service == NULL) + return -ENOMEM; - (*generated_service)->modification_time = original_service_stat.st_mtime; - HASH_ADD_STR(generated_services, original_path, *generated_service); + strcpy((*generated_service)->original_path, full_service_path); + + switch (init_stratum_service_type) { + case SERVICE_TYPE_SYSTEMD: + ; + int rv = generate_systemd_service(&service_desc, *generated_service); + if (rv < 0) + return rv; + + break; } } @@ -216,7 +251,7 @@ static inline int read_service(struct cfg_entry *cfg, const char *const ipath, s break; } } else { - struct h_generated_service *generated_service = NULL; + struct generated_service *generated_service = NULL; generate_service_for(back, bpath, service_type, &generated_service); strncpy(buf, generated_service->service_text, generated_service->service_text_len); diff --git a/src/slash-bedrock/etc/bedrock.conf b/src/slash-bedrock/etc/bedrock.conf index 0c664417..b986bf54 100644 --- a/src/slash-bedrock/etc/bedrock.conf +++ b/src/slash-bedrock/etc/bedrock.conf @@ -447,6 +447,8 @@ fonts = /usr/share/fonts [cross-service] usr/lib/systemd = /usr/lib/systemd +etc/sv = /etc/sv +etc/init.d = /etc/init.d [pmm] #