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.
This commit is contained in:
Roy Marples 2009-05-02 12:26:45 +01:00
parent e040bd77e9
commit 6615eb4b68
11 changed files with 217 additions and 26 deletions

View File

@ -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

View File

@ -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"

View File

@ -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)
{

View File

@ -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)

View File

@ -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 */

View File

@ -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;

View File

@ -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);
}

View File

@ -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 <runlevel>\n" \
" rc-update [options] del service <runlevel>\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,10 +234,13 @@ 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);

View File

@ -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)

View File

@ -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

View File

@ -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