From 6615eb4b689d7aa1d047bd9ed75eca80beac9767 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Sat, 2 May 2009 12:26:45 +0100 Subject: [PATCH] Add runlevel stacking, #88 This implementation has the limitation that you cannot have a stacked runlevel and service of the same name in a runlevel. --- man/rc-update.8 | 14 ++++-- man/rc.8 | 9 ++-- src/librc/librc.c | 70 ++++++++++++++++++++++++++- src/librc/librc.h | 4 ++ src/librc/rc.h.in | 21 ++++++++ src/librc/rc.map | 4 ++ src/rc/rc-status.c | 24 +++++++-- src/rc/rc-update.c | 83 +++++++++++++++++++++++++++----- src/rc/rc.c | 2 +- src/test/librc.funcs.hidden.list | 4 ++ src/test/rc.funcs.list | 8 +++ 11 files changed, 217 insertions(+), 26 deletions(-) diff --git a/man/rc-update.8 b/man/rc-update.8 index 6cad29e5..6fad02c4 100644 --- a/man/rc-update.8 +++ b/man/rc-update.8 @@ -1,4 +1,4 @@ -.\" Copyright (c) 2007-2009 Roy Marples +4.\" Copyright (c) 2007-2009 Roy Marples .\" All rights reserved .\" .\" Redistribution and use in source and binary forms, with or without @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd Jan 10, 2009 +.Dd May 2, 2009 .Dt RC-UPDATE 8 SMM .Os OpenRC .Sh NAME @@ -30,10 +30,12 @@ .Nd add and remove services to and from a runlevel .Sh SYNOPSIS .Nm +.Op Fl s , -stack .Ar add .Ar service .Op Ar runlevel ... .Nm +.Op Fl s , -stack .Ar delete .Ar service .Op Ar runlevel ... @@ -51,7 +53,8 @@ All services must reside in the .Pa /etc/init.d or .Pa /usr/local/etc/init.d -directories. They must also conform to the OpenRC runscript standard. +directories. +They must also conform to the OpenRC runscript standard. .Pp .Bl -tag -width "Fl a , -delete service" .It Ar add Ar service @@ -78,6 +81,11 @@ Forces an update of the dependency tree cache. This may be needed in the event of clock skew (a file in /etc is newer than the system clock). .El +.Pp +If the +.Fl s , -stack +option is given then we either add or remove the runlevel from the runlevel. +This allows inheritance of runlevels. .Sh SEE ALSO .Xr rc 8 , .Xr rc-status 8 diff --git a/man/rc.8 b/man/rc.8 index 1e97762b..ab161fe7 100644 --- a/man/rc.8 +++ b/man/rc.8 @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd January 13, 2009 +.Dd May 2, 2009 .Dt RC 8 SMM .Os OpenRC .Sh NAME @@ -35,10 +35,11 @@ .Sh DESCRIPTION .Nm first stops any services that are not for the runlevel and then starts any -services added by +services in the runlevel and from stacked runlevels added by .Nm rc-update -that are not currently started. If no runlevel is specified then we use the -current runlevel the system is currently in. +that are not currently started. +If no runlevel is specified then we use the current runlevel the system +is currently in. .Pp There are some special runlevels that you should be aware of: .Bl -tag -width "shutdown" diff --git a/src/librc/librc.c b/src/librc/librc.c index 05c1c3d0..f73936d9 100644 --- a/src/librc/librc.c +++ b/src/librc/librc.c @@ -66,7 +66,7 @@ static const rc_service_state_name_t rc_service_state_names[] = { }; #define LS_INITD 0x01 -#define LS_DIR 0x02 +#define LS_DIR 0x02 static RC_STRINGLIST * ls_dir(const char *dir, int options) { @@ -102,7 +102,7 @@ ls_dir(const char *dir, int options) } if (options & LS_DIR) { if (stat(d->d_name, &buf) == 0 && - ! S_ISDIR(buf.st_mode)) + !S_ISDIR(buf.st_mode)) continue; } rc_stringlist_add(list, d->d_name); @@ -330,6 +330,51 @@ rc_runlevel_exists(const char *runlevel) } librc_hidden_def(rc_runlevel_exists) +bool +rc_runlevel_stack(const char *dst, const char *src) +{ + char d[PATH_MAX], s[PATH_MAX]; + + if (!rc_runlevel_exists(dst) || !rc_runlevel_exists(src)) + return false; + snprintf(s, sizeof(s), "../%s", src); + snprintf(d, sizeof(s), "%s/%s/%s", RC_RUNLEVELDIR, dst, src); + return (symlink(s, d) == 0 ? true : false); +} +librc_hidden_def(rc_runlevel_stack) + +bool +rc_runlevel_unstack(const char *dst, const char *src) +{ + char path[PATH_MAX]; + + snprintf(path, sizeof(path), "%s/%s/%s", RC_RUNLEVELDIR, dst, src); + return (unlink(path) == 0 ? true : false); +} +librc_hidden_def(rc_runlevel_unstack) + +RC_STRINGLIST * +rc_runlevel_stacks(const char *runlevel) +{ + char path[PATH_MAX]; + RC_STRINGLIST *dirs; + RC_STRING *d, *dn; + + if (!runlevel) + return false; + snprintf(path, sizeof(path), "%s/%s", RC_RUNLEVELDIR, runlevel); + dirs = ls_dir(path, LS_DIR); + TAILQ_FOREACH_SAFE(d, dirs, entries, dn) { + if (!rc_runlevel_exists(d->value)) { + TAILQ_REMOVE(dirs, d, entries); + free(d->value); + free(d); + } + } + return dirs; +} +librc_hidden_def(rc_runlevel_stacks) + /* Resolve a service name to it's full path */ char * rc_service_resolve(const char *service) @@ -780,6 +825,27 @@ rc_services_in_runlevel(const char *runlevel) } librc_hidden_def(rc_services_in_runlevel) +RC_STRINGLIST * +rc_services_in_runlevel_stacked(const char *runlevel) +{ + RC_STRINGLIST *list, *stacks, *sl; + RC_STRING *stack; + + list = rc_services_in_runlevel(runlevel); + stacks = rc_runlevel_stacks(runlevel); + TAILQ_FOREACH (stack, stacks, entries) { + sl = rc_services_in_runlevel(stack->value); + if (list != NULL) { + TAILQ_CONCAT(list, sl, entries); + free(sl); + } else + list = sl; + } + return list; +} +librc_hidden_def(rc_services_in_runlevel_stacked) + + RC_STRINGLIST * rc_services_in_state(RC_SERVICE state) { diff --git a/src/librc/librc.h b/src/librc/librc.h index ba7da315..d2501c06 100644 --- a/src/librc/librc.h +++ b/src/librc/librc.h @@ -91,8 +91,11 @@ librc_hidden_proto(rc_runlevel_exists) librc_hidden_proto(rc_runlevel_get) librc_hidden_proto(rc_runlevel_list) librc_hidden_proto(rc_runlevel_set) +librc_hidden_proto(rc_runlevel_stack) +librc_hidden_proto(rc_runlevel_stacks) librc_hidden_proto(rc_runlevel_starting) librc_hidden_proto(rc_runlevel_stopping) +librc_hidden_proto(rc_runlevel_unstack) librc_hidden_proto(rc_service_add) librc_hidden_proto(rc_service_daemons_crashed) librc_hidden_proto(rc_service_daemon_set) @@ -106,6 +109,7 @@ librc_hidden_proto(rc_service_resolve) librc_hidden_proto(rc_service_schedule_clear) librc_hidden_proto(rc_service_schedule_start) librc_hidden_proto(rc_services_in_runlevel) +librc_hidden_proto(rc_services_in_runlevel_stacked) librc_hidden_proto(rc_services_in_state) librc_hidden_proto(rc_services_scheduled) librc_hidden_proto(rc_services_scheduled_by) diff --git a/src/librc/rc.h.in b/src/librc/rc.h.in index 82874156..cb5258c5 100644 --- a/src/librc/rc.h.in +++ b/src/librc/rc.h.in @@ -80,6 +80,22 @@ char *rc_runlevel_get(void); * @return true if the runlevel exists, otherwise false */ bool rc_runlevel_exists(const char *); +/*! Stack a runlevel onto another + * @param runlevel to stack onto + * @param runlevel being stacked + * @return true if successful, otherwise false */ +bool rc_runlevel_stack(const char *, const char *); + +/*! Unstack a runlevel from another + * @param runlevel to unstack from + * @param runlevel being unstacked + * @return true if successful, otherwise false */ +bool rc_runlevel_unstack(const char *, const char *); + +/*! Return a NULL terminated list of runlevel stacks in the runlevels + * @return a NULL terminated list of runlevels */ +RC_STRINGLIST *rc_runlevel_stacks(const char *); + /*! Return a NULL terminated list of runlevels * @return a NULL terminated list of runlevels */ RC_STRINGLIST *rc_runlevel_list(void); @@ -225,6 +241,11 @@ bool rc_service_value_set(const char *, const char *, const char *); * @return NULL terminated list of services */ RC_STRINGLIST *rc_services_in_runlevel(const char *); +/*! List the stacked services in a runlevel + * @param runlevel to list + * @return NULL terminated list of services */ +RC_STRINGLIST *rc_services_in_runlevel_stacked(const char *); + /*! List the services in a state * @param state to list * @return NULL terminated list of services */ diff --git a/src/librc/rc.map b/src/librc/rc.map index e9fed69b..7cfbfabc 100644 --- a/src/librc/rc.map +++ b/src/librc/rc.map @@ -18,8 +18,11 @@ global: rc_runlevel_get; rc_runlevel_list; rc_runlevel_set; + rc_runlevel_stack; + rc_runlevel_stacks; rc_runlevel_starting; rc_runlevel_stopping; + rc_runlevel_unstack; rc_service_add; rc_service_daemons_crashed; rc_service_daemon_set; @@ -34,6 +37,7 @@ global: rc_service_schedule_clear; rc_service_schedule_start; rc_services_in_runlevel; + rc_services_in_runlevel_stacked; rc_services_in_state; rc_services_scheduled; rc_services_scheduled_by; diff --git a/src/rc/rc-status.c b/src/rc/rc-status.c index 4c6e5129..0a232777 100644 --- a/src/rc/rc-status.c +++ b/src/rc/rc-status.c @@ -78,8 +78,10 @@ _rc_can_find_pids(void) } static void -print_level(const char *level) +print_level(const char *prefix, const char *level) { + if (prefix) + printf("%s ", prefix); printf ("Runlevel: "); if (isatty(fileno(stdout))) printf("%s%s%s\n", @@ -274,16 +276,28 @@ rc_status(int argc, char **argv) deptree = _rc_deptree_load(0, NULL); TAILQ_FOREACH(l, levels, entries) { - print_level(l->value); + print_level(NULL, l->value); services = rc_services_in_runlevel(l->value); print_services(l->value, services); + nservices = rc_runlevel_stacks(l->value); + TAILQ_FOREACH(s, nservices, entries) { + if (rc_stringlist_find(levels, s->value) != NULL) + continue; + print_level("Stacked", s->value); + sservices = rc_services_in_runlevel(s->value); + print_services(s->value, sservices); + rc_stringlist_free(sservices); + } + sservices = NULL; + rc_stringlist_free(nservices); + nservices = NULL; rc_stringlist_free(services); services = NULL; } if (aflag || argc < 2) { /* Show hotplugged services */ - print_level("hotplugged"); + print_level("Dynamic", "hotplugged"); services = rc_services_in_state(RC_SERVICE_HOTPLUGGED); print_services(NULL, services); rc_stringlist_free(services); @@ -336,9 +350,9 @@ rc_status(int argc, char **argv) rc_stringlist_free(tmp); } l->value = p; - print_level("needed"); + print_level("Dynamic", "needed"); print_services(NULL, nservices); - print_level("manual"); + print_level("Dynamic", "manual"); print_services(NULL, services); } diff --git a/src/rc/rc-update.c b/src/rc/rc-update.c index f37f6f02..46adc635 100644 --- a/src/rc/rc-update.c +++ b/src/rc/rc-update.c @@ -63,11 +63,11 @@ add(const char *runlevel, const char *service) eerror("%s: service `%s' does not exist", applet, service); } else if (rc_service_in_runlevel(service, runlevel)) { - ewarn ("%s: %s already installed in runlevel `%s'; skipping", + ewarn("%s: %s already installed in runlevel `%s'; skipping", applet, service, runlevel); retval = 0; } else if (rc_service_add(runlevel, service)) { - einfo ("%s added to runlevel %s", service, runlevel); + einfo("service %s added to runlevel %s", service, runlevel); retval = 1; } else eerror("%s: failed to add service `%s' to runlevel `%s': %s", @@ -83,20 +83,76 @@ delete(const char *runlevel, const char *service) errno = 0; if (rc_service_delete(runlevel, service)) { - einfo("%s removed from runlevel %s", service, runlevel); + einfo("service %s removed from runlevel %s", + service, runlevel); return 1; } if (errno == ENOENT) - eerror ("%s: service `%s' is not in the runlevel `%s'", + eerror("%s: service `%s' is not in the runlevel `%s'", applet, service, runlevel); else - eerror ("%s: failed to remove service `%s' from runlevel `%s': %s", + eerror("%s: failed to remove service `%s' from runlevel `%s': %s", applet, service, runlevel, strerror (errno)); return retval; } +static int +addstack(const char *runlevel, const char *stack) +{ + if (!rc_runlevel_exists(runlevel)) { + eerror("%s: runlevel `%s' does not exist", applet, runlevel); + return -1; + } + if (!rc_runlevel_exists(stack)) { + eerror("%s: runlevel `%s' does not exist", applet, stack); + return -1; + } + if (strcmp(runlevel, stack) == 0) { + eerror("%s: cannot stack `%s' onto itself", applet, stack); + return -1; + } + if (strcmp(runlevel, RC_LEVEL_SYSINIT) == 0 || + strcmp(stack, RC_LEVEL_SYSINIT) == 0 || + strcmp(runlevel, RC_LEVEL_BOOT) == 0 || + strcmp(stack, RC_LEVEL_BOOT) == 0 || + strcmp(runlevel, RC_LEVEL_SINGLE) == 0 || + strcmp(stack, RC_LEVEL_SINGLE) == 0 || + strcmp(runlevel, RC_LEVEL_SHUTDOWN) == 0 || + strcmp(stack, RC_LEVEL_SHUTDOWN) == 0) + { + eerror("%s: cannot stack the %s runlevel", + applet, RC_LEVEL_SYSINIT); + return -1; + } + if (!rc_runlevel_stack(runlevel, stack)) { + eerror("%s: failed to stack `%s' to `%s': %s", + applet, stack, runlevel, strerror(errno)); + return -1; + } + einfo("runlevel %s added to runlevel %s", stack, runlevel); + return 1; +} + +static int +delstack(const char *runlevel, const char *stack) +{ + if (rc_runlevel_unstack(runlevel, stack)) { + einfo("runlevel %s removed from runlevel %s", stack, runlevel); + return 1; + } + + if (errno == ENOENT) + eerror("%s: runlevel `%s' is not in the runlevel `%s'", + applet, stack, runlevel); + else + eerror("%s: failed to remove runlevel `%s' from runlevel `%s': %s", + applet, stack, runlevel, strerror (errno)); + + return -1; +} + static void show(RC_STRINGLIST *runlevels, bool verbose) { @@ -143,12 +199,14 @@ show(RC_STRINGLIST *runlevels, bool verbose) "Usage: rc-update [options] add service \n" \ " rc-update [options] del service \n" \ " rc-update [options] show" -#define getoptstring "u" getoptstring_COMMON +#define getoptstring "su" getoptstring_COMMON static const struct option longopts[] = { + { "stack", 0, NULL, 's' }, { "update", 0, NULL, 'u' }, longopts_COMMON }; static const char * const longopts_help[] = { + "Stack a runlevel instead of a service", "Force an update of the dependency tree", longopts_help_COMMON }; @@ -166,7 +224,7 @@ rc_update(int argc, char **argv) char *service = NULL; char *p; int action = 0; - bool verbose = false; + bool verbose = false, stack = false; int opt; int retval = EXIT_FAILURE; int num_updated = 0; @@ -176,11 +234,14 @@ rc_update(int argc, char **argv) while ((opt = getopt_long(argc, argv, getoptstring, longopts, (int *)0)) != -1) switch (opt) { + case 's': + stack = true; + break; case 'u': _rc_deptree_load(-1, &ret); return ret; - case_RC_COMMON_GETOPT - } + case_RC_COMMON_GETOPT; + } verbose = rc_yesno(getenv ("EINFO_VERBOSE")); @@ -241,9 +302,9 @@ rc_update(int argc, char **argv) eerror ("%s: no service specified", applet); else { if (action & DOADD) { - actfunc = add; + actfunc = stack ? addstack : add; } else if (action & DODELETE) { - actfunc = delete; + actfunc = stack ? delstack : delete; } else { rc_stringlist_free(runlevels); eerrorx("%s: invalid action", applet); diff --git a/src/rc/rc.c b/src/rc/rc.c index 78cfcae4..8e40e1d9 100644 --- a/src/rc/rc.c +++ b/src/rc/rc.c @@ -1037,7 +1037,7 @@ main(int argc, char **argv) /* Load our list of start services */ hotplugged_services = rc_services_in_state(RC_SERVICE_HOTPLUGGED); - start_services = rc_services_in_runlevel(newlevel ? + start_services = rc_services_in_runlevel_stacked(newlevel ? newlevel : runlevel); if (strcmp(newlevel ? newlevel : runlevel, RC_LEVEL_SHUTDOWN) != 0 && strcmp(newlevel ? newlevel : runlevel, RC_LEVEL_SYSINIT) != 0) diff --git a/src/test/librc.funcs.hidden.list b/src/test/librc.funcs.hidden.list index 2416a0c2..121bfcf0 100644 --- a/src/test/librc.funcs.hidden.list +++ b/src/test/librc.funcs.hidden.list @@ -16,8 +16,11 @@ rc_runlevel_exists rc_runlevel_get rc_runlevel_list rc_runlevel_set +rc_runlevel_stack +rc_runlevel_stacks rc_runlevel_starting rc_runlevel_stopping +rc_runlevel_unstack rc_service_add rc_service_daemon_set rc_service_daemons_crashed @@ -35,6 +38,7 @@ rc_service_state rc_service_value_get rc_service_value_set rc_services_in_runlevel +rc_services_in_runlevel_stacked rc_services_in_state rc_services_scheduled rc_services_scheduled_by diff --git a/src/test/rc.funcs.list b/src/test/rc.funcs.list index f6324741..409ede0d 100644 --- a/src/test/rc.funcs.list +++ b/src/test/rc.funcs.list @@ -32,10 +32,16 @@ rc_runlevel_list rc_runlevel_list@@RC_1.0 rc_runlevel_set rc_runlevel_set@@RC_1.0 +rc_runlevel_stack +rc_runlevel_stack@@RC_1.0 +rc_runlevel_stacks +rc_runlevel_stacks@@RC_1.0 rc_runlevel_starting rc_runlevel_starting@@RC_1.0 rc_runlevel_stopping rc_runlevel_stopping@@RC_1.0 +rc_runlevel_unstack +rc_runlevel_unstack@@RC_1.0 rc_service_add rc_service_add@@RC_1.0 rc_service_daemon_set @@ -70,6 +76,8 @@ rc_service_value_set rc_service_value_set@@RC_1.0 rc_services_in_runlevel rc_services_in_runlevel@@RC_1.0 +rc_services_in_runlevel_stacked +rc_services_in_runlevel_stacked@@RC_1.0 rc_services_in_state rc_services_in_state@@RC_1.0 rc_services_scheduled