diff --git a/man/openrc-run.8 b/man/openrc-run.8 index ae18ffdb8..247119bc9 100644 --- a/man/openrc-run.8 +++ b/man/openrc-run.8 @@ -430,6 +430,20 @@ for later retrieval. Saved values are lost when the service stops. .It Ic service_get_value Ar name Returns the saved value called .Ar name . +.It Xo +.Ic service_export Ar NAME Ns Op = Ns Ar VALUE +.Ar ... +.Xc +Exports +.Ar NAME +for dependee services. Exported variables are lost when the service stops. + +If +.Ar VALUE +is not provided, it's taken from the environment. + +Exported variables only take effect when dependees start, so it makes +little sense to export anywhere but on start_pre, start, or start_post. .It Ic service_started Op Ar service If the service is started, return 0 otherwise 1. .It Ic service_starting Op Ar service diff --git a/meson.build b/meson.build index c1566f9fb..b7e65e4ca 100644 --- a/meson.build +++ b/meson.build @@ -158,7 +158,7 @@ if not cc.has_function('pipe2', prefix: '#include ') endif endif -add_project_arguments(feature_macros.keys(), language: 'c') +add_project_arguments([feature_macros.keys(), '-Drc_private='], language: 'c') incdir = include_directories('src/shared') einfo_incdir = include_directories('src/libeinfo') diff --git a/src/librc/librc-misc.c b/src/librc/librc-misc.c index 509446584..cfb5e172d 100644 --- a/src/librc/librc-misc.c +++ b/src/librc/librc-misc.c @@ -55,35 +55,25 @@ rc_yesno(const char *value) return false; } - -/** - * Read the entire @file into the buffer and set @len to the - * size of the buffer when finished. For C strings, this will - * be strlen(buffer) + 1. - * Don't forget to free the buffer afterwards! - */ bool -rc_getfile(const char *file, char **buffer, size_t *len) +rc_getfileat(int dirfd, const char *file, char **buffer, size_t *size) { bool ret = false; FILE *fp; - int fd; struct stat st; size_t done, left; - fp = fopen(file, "re"); + fp = do_fopenat(dirfd, file, O_RDONLY); if (!fp) return false; /* assume fileno() never fails */ - fd = fileno(fp); - - if (fstat(fd, &st)) + if (fstat(fileno(fp), &st)) goto finished; left = st.st_size; - *len = left + 1; /* NUL terminator */ - *buffer = xrealloc(*buffer, *len); + *size = left + 1; /* NUL terminator */ + *buffer = xrealloc(*buffer, *size); while (left) { done = fread(*buffer, sizeof(*buffer[0]), left, fp); if (done == 0 && ferror(fp)) @@ -92,16 +82,28 @@ rc_getfile(const char *file, char **buffer, size_t *len) } ret = true; - finished: +finished: if (!ret) { free(*buffer); - *len = 0; + *size = 0; } else - (*buffer)[*len - 1] = '\0'; + (*buffer)[*size - 1] = '\0'; fclose(fp); return ret; } +/** + * Read the entire @file into the buffer and set @len to the + * size of the buffer when finished. For C strings, this will + * be strlen(buffer) + 1. + * Don't forget to free the buffer afterwards! + */ +bool +rc_getfile(const char *file, char **buffer, size_t *len) +{ + return rc_getfileat(AT_FDCWD, file, buffer, len); +} + char * rc_proc_getent(const char *ent RC_UNUSED) { diff --git a/src/librc/librc.c b/src/librc/librc.c index ecadd5a82..62f4c5fcf 100644 --- a/src/librc/librc.c +++ b/src/librc/librc.c @@ -964,6 +964,7 @@ rc_service_mark(const char *service, const RC_SERVICE state) if (state == RC_SERVICE_STOPPED) { rm_dir(rc_dirfd(RC_DIR_OPTIONS), base, true); rm_dir(rc_dirfd(RC_DIR_DAEMONS), base, true); + rm_dir(rc_dirfd(RC_DIR_ENVIRONMENT), base, true); rc_service_schedule_clear(service); } @@ -1268,3 +1269,68 @@ rc_services_scheduled(const char *service) { return ls_dir(rc_dirfd(RC_DIR_SCHEDULED), basename_c(service), LS_INITD); } + +bool +rc_service_setenv(const char *service, const char *name, const char *value) +{ + FILE *envfile; + int dirfd; + + if (!service) + service = "rc"; + + if (mkdirat(rc_dirfd(RC_DIR_ENVIRONMENT), service, 0644) == -1 && errno != EEXIST) + return false; + if ((dirfd = openat(rc_dirfd(RC_DIR_ENVIRONMENT), service, O_RDONLY | O_DIRECTORY)) == -1) + return false; + + if (!value) { + bool ret = unlinkat(dirfd, name, 0) == 0; + close(dirfd); + return ret; + } + + envfile = do_fopenat(dirfd, name, O_WRONLY | O_TRUNC | O_CREAT); + close(dirfd); + + if (!envfile) + return false; + + fputs(value, envfile); + return fclose(envfile) == 0; +} + +bool +rc_environ_open(struct rc_environ *env, const char *service) +{ + DIR *envdir = do_opendirat(rc_dirfd(RC_DIR_ENVIRONMENT), service ? basename_c(service) : "rc"); + if (!envdir) + return false; + *env = (struct rc_environ) { .envdir = envdir }; + return true; +} + +bool +rc_environ_get(struct rc_environ *env, const char **name, const char **value) +{ + for (struct dirent *entry; (entry = readdir(env->envdir));) { + if (entry->d_name[0] == '.') + continue; + + if (!rc_getfileat(dirfd(env->envdir), entry->d_name, &env->bytes, &env->size)) + continue; + + *name = entry->d_name; + *value = env->bytes; + return true; + } + + return false; +} + +void +rc_environ_close(struct rc_environ *env) +{ + closedir(env->envdir); + free(env->bytes); +} diff --git a/src/librc/librc.h b/src/librc/librc.h index c3c0d850f..2f4ff4543 100644 --- a/src/librc/librc.h +++ b/src/librc/librc.h @@ -70,10 +70,12 @@ static const char *const dirnames[RC_DIR_SYS_MAX] = [RC_DIR_OPTIONS] = "options", [RC_DIR_EXCLUSIVE] = "exclusive", [RC_DIR_SCHEDULED] = "scheduled", + [RC_DIR_ENVIRONMENT] = "environment", [RC_DIR_INITD] = "init.d", [RC_DIR_TMP] = "tmp", }; +bool do_getfileat(int dirfd, const char *file, char **buffer, size_t *size); RC_STRINGLIST *config_list(int dirfd, const char *pathname); void clear_dirfds(void); diff --git a/src/librc/rc.h.in b/src/librc/rc.h.in index 096e3b58b..f987f4bb7 100644 --- a/src/librc/rc.h.in +++ b/src/librc/rc.h.in @@ -14,6 +14,7 @@ #define __RC_H__ #include +#include #include #include @@ -62,6 +63,10 @@ extern "C" { # define RC_LOCAL_CONFDIR RC_LOCAL_PREFIX "/etc/conf.d" #endif +#ifndef rc_private +#define rc_private __attribute__((deprecated("private field"))) +#endif + #ifndef _SYS_QUEUE_H_ /* @@ -209,6 +214,7 @@ enum rc_dir { RC_DIR_OPTIONS, RC_DIR_EXCLUSIVE, RC_DIR_SCHEDULED, + RC_DIR_ENVIRONMENT, RC_DIR_INITD, RC_DIR_TMP, RC_DIR_SYS_MAX, @@ -373,6 +379,38 @@ RC_STRINGLIST *rc_services_scheduled(const char *); * @return true if all daemons started are still running, otherwise false */ bool rc_service_daemons_crashed(const char *); +/*! Sets a variable into the enviroment for a given service. + * @param service name, if null, sets the variable to the global environment. + * @param variable name. + * @param variable value. + * @return true on success, false on IO error. */ +bool rc_service_setenv(const char *, const char *, const char *); + +struct rc_environ { + rc_private DIR *envdir; + rc_private char *bytes; + rc_private size_t size; +}; + +/*! Opens the environment of a given service. + * @param environ to initialize. + * @param serivce name. + * @return true on success, false on IO error. */ +bool rc_environ_open(struct rc_environ *, const char *); + +/*! Gets the environment of a given service. the name and value are only + * valid until the next rc_environ_get or rc_environ_close call with the + * same environment object. + * @param environment to iterate. + * @param out variable name. + * @param out variable value. + * @return true on success, false when there's no more variables. */ +bool rc_environ_get(struct rc_environ *, const char **, const char **); + +/*! Closes the environment and frees the resources. + * @param environ to cleanup. */ +void rc_environ_close(struct rc_environ *); + /*! @name System types * OpenRC can support some special sub system types, normally virtualization. * Some services cannot work in these systems, or we do something else. */ @@ -661,6 +699,7 @@ typedef LIST_HEAD(rc_pidlist, rc_pid) RC_PIDLIST; RC_PIDLIST *rc_find_pids(const char *, const char *const *, uid_t, pid_t); /* Basically the same as getline(), it just returns multiple lines */ +bool rc_getfileat(int, const char *, char **, size_t *); bool rc_getfile(const char *, char **, size_t *); /* __END_DECLS */ diff --git a/src/meson.build b/src/meson.build index 5e5af6496..a62514cae 100644 --- a/src/meson.build +++ b/src/meson.build @@ -23,6 +23,7 @@ subdir('pam_openrc') subdir('poweroff') subdir('rc-abort') subdir('rc-depend') +subdir('rc-env') subdir('rc-service') subdir('rc-sstat') subdir('rc-status') diff --git a/src/openrc-run/openrc-run.c b/src/openrc-run/openrc-run.c index 5dab4a594..d78209182 100644 --- a/src/openrc-run/openrc-run.c +++ b/src/openrc-run/openrc-run.c @@ -175,6 +175,20 @@ unhotplug(void) eerror("%s: unlink '%s/hotplugged/%s': %s", applet, rc_svcdir(), applet, strerror(errno)); } +static void +add_environment(const char *svcname) +{ + struct rc_environ svc_env; + + if (!rc_environ_open(&svc_env, svcname)) + return; + + for (const char *name, *value; rc_environ_get(&svc_env, &name, &value);) + setenv(name, value, true); + + rc_environ_close(&svc_env); +} + static void start_services(RC_STRINGLIST *list) { @@ -676,8 +690,10 @@ svc_start_deps(void) tmplist = rc_stringlist_new(); TAILQ_FOREACH(svc, services, entries) { state = rc_service_state(svc->value); - if (state & RC_SERVICE_STARTED) + if (state & RC_SERVICE_STARTED) { + add_environment(svc->value); continue; + } /* Don't wait for services which went inactive but are * now in starting state which we are after */ @@ -694,8 +710,10 @@ svc_start_deps(void) eerror("%s: timed out waiting for %s", applet, svc->value); state = rc_service_state(svc->value); - if (state & RC_SERVICE_STARTED) + if (state & RC_SERVICE_STARTED) { + add_environment(svc->value); continue; + } if (rc_stringlist_find(need_services, svc->value)) { if (state & RC_SERVICE_INACTIVE || state & RC_SERVICE_WASINACTIVE) @@ -800,6 +818,8 @@ static void svc_start_real(void) static int svc_start(void) { + add_environment(NULL); + if (dry_run) einfon("start:"); else @@ -912,8 +932,9 @@ svc_stop_deps(RC_SERVICE state) return; TAILQ_FOREACH(svc, tmplist, entries) { - if (rc_service_state(svc->value) & RC_SERVICE_STOPPED) + if (rc_service_state(svc->value) & RC_SERVICE_STOPPED) { continue; + } svc_wait(svc->value); if (rc_service_state(svc->value) & RC_SERVICE_STOPPED) continue; @@ -984,9 +1005,10 @@ svc_stop_real(void) static int svc_stop(void) { - RC_SERVICE state; + RC_SERVICE state = 0; + + add_environment(NULL); - state = 0; if (dry_run) einfon("stop:"); else diff --git a/src/rc-env/meson.build b/src/rc-env/meson.build new file mode 100644 index 000000000..582d094f2 --- /dev/null +++ b/src/rc-env/meson.build @@ -0,0 +1,5 @@ +executable('rc-env', 'rc-env.c', + include_directories: incdir, + dependencies: [rc, einfo, shared], + install: true, + install_dir: sbindir) diff --git a/src/rc-env/rc-env.c b/src/rc-env/rc-env.c new file mode 100644 index 000000000..1ef6dcf87 --- /dev/null +++ b/src/rc-env/rc-env.c @@ -0,0 +1,111 @@ +#include +#include + +#include "helpers.h" +#include "queue.h" +#include "_usage.h" + +const char *applet = NULL; +const char *extraopts = NULL; +const char *usagestring = "Usage: rc-env [options] [...]\n"; +const char getoptstring[] = "s:u" getoptstring_COMMON; +const struct option longopts[] = { + { "service", required_argument, NULL, 's' }, + { "unset", no_argument, NULL, 'u' }, + longopts_COMMON +}; +const char * const longopts_help[] = { + "Service whose variables to print", + "Unset listed varibles", + longopts_help_COMMON +}; + +static void escape_variable(const char *value) { + char ch; + + printf("$'"); + for (const char *p = value; *p; p++) { + switch ((ch = *p)) { + case '\a': ch = 'a'; break; + case '\b': ch = 'b'; break; + case '\f': ch = 'f'; break; + case '\n': ch = 'n'; break; + case '\r': ch = 'r'; break; + case '\t': ch = 't'; break; + case '\v': ch = 'v'; break; + /* \e is non-standard. */ + case 0x1b: ch = 'e'; break; + case '"': + case '\'': + case '\\': + break; + default: + putchar(ch); + continue; + } + printf("\\%c", ch); + } + puts("\'\n"); +} + +static void +print_environment(const char *service) +{ + /* from POSIX.2024 Shell Command Language 2.2 */ + static const char special_chars[] = "|&;<>()$`\\\"' \t\n*?[]^-!#~=%{},"; + struct rc_environ env; + + if (!rc_environ_open(&env, service)) + return; + + for (const char *name, *value; rc_environ_get(&env, &name, &value);) { + if (strpbrk(value, special_chars)) + escape_variable(value); + else + puts(value); + } + + rc_environ_close(&env); +} + +int main(int argc, char **argv) { + RC_STRINGLIST *services = NULL; + RC_STRING *service; + bool unset = false; + int opt; + + applet = basename_c(argv[0]); + while ((opt = getopt_long(argc, argv, getoptstring, longopts, NULL)) != -1) { + switch (opt) { + case 'u': unset = true; break; + case 's': + if (!services) + services = rc_stringlist_new(); + rc_stringlist_addu(services, optarg); + break; + case_RC_COMMON_GETOPT + } + } + + argc -= optind; + argv += optind; + + if (!argc) { + if (!services) { + services = rc_services_in_state(RC_SERVICE_STARTED); + print_environment("rc"); + } + TAILQ_FOREACH(service, services, entries) + print_environment(service->value); + rc_stringlist_free(services); + } else for (int i = 0; i < argc; i++) { + char *value = argv[i], *name = strsep(&value, "="); + + if (unset && value) + eerror("invalid variable name '%s=%s'", name, value); + else if (!unset && !value && !(value = getenv(name))) + ewarn("no value found for variable '%s'", name); + else + rc_service_setenv(NULL, name, value); + } +} diff --git a/src/value/meson.build b/src/value/meson.build index af48c08ed..ecf563644 100644 --- a/src/value/meson.build +++ b/src/value/meson.build @@ -1,6 +1,7 @@ value_execs = [ 'service_get_value', 'service_set_value', + 'service_export', 'get_options', 'save_options', ] diff --git a/src/value/value.c b/src/value/value.c index c9620cf0c..091b15f18 100644 --- a/src/value/value.c +++ b/src/value/value.c @@ -25,9 +25,9 @@ const char *applet = NULL; int main(int argc, char **argv) { - bool ok = false; char *service = getenv("RC_SVCNAME"); - char *option; + enum { GET, SET, EXPORT } action; + char *option = NULL; applet = basename_c(argv[0]); if (service == NULL) @@ -36,23 +36,34 @@ int main(int argc, char **argv) if (rc_yesno(getenv("RC_USER_SERVICES"))) rc_set_user(); - if (argc < 2 || !argv[1] || *argv[1] == '\0') - eerrorx("%s: no option specified", applet); - - if (strcmp(applet, "service_get_value") == 0 || - strcmp(applet, "get_options") == 0) - { - option = rc_service_value_get(service, argv[1]); - if (option) { - printf("%s", option); - free(option); - ok = true; - } - } else if (strcmp(applet, "service_set_value") == 0 || - strcmp(applet, "save_options") == 0) - ok = rc_service_value_set(service, argv[1], argv[2]); + if (strcmp(applet, "service_get_value") == 0 || strcmp(applet, "get_options") == 0) + action = GET; + else if (strcmp(applet, "service_set_value") == 0 || strcmp(applet, "save_options") == 0) + action = SET; + else if (strcmp(applet, "service_export") == 0) + action = EXPORT; else eerrorx("%s: unknown applet", applet); - return ok ? EXIT_SUCCESS : EXIT_FAILURE; + if (argc < 2 || !argv[1] || *argv[1] == '\0') + eerrorx("%s: no %s specified", applet, action == EXPORT ? "variable" : "option"); + + switch (action) { + case GET: + if (!(option = rc_service_value_get(service, argv[1]))) + return EXIT_FAILURE; + printf("%s", option); + free(option); + return EXIT_SUCCESS; + case SET: + return rc_service_value_set(service, argv[1], argv[2]) ? EXIT_SUCCESS : EXIT_FAILURE; + case EXPORT: + for (int i; i < argc; i++) { + char *value = argv[i], *name = strsep(&value, "="); + if (!value && !(value = getenv(name))) + ewarn("%s not found.", name); + else + rc_service_setenv(service, name, value); + } + } }