Add -Z, --dry-run option to show which services we would start/stop

without actually doing so.
Fixes #151.
This commit is contained in:
Roy Marples 2009-04-30 23:42:01 +01:00
parent 3d0e5175d8
commit f689187966
2 changed files with 314 additions and 263 deletions

View File

@ -22,7 +22,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd April 26, 2009
.Dd April 30, 2009
.Dt RUNSCRIPT 8 SMM
.Os OpenRC
.Sh NAME
@ -33,6 +33,7 @@
.Op Fl D , -nodeps
.Op Fl d , -debug
.Op Fl s , -ifstarted
.Op Fl Z , -dry-run
.Op Ar command ...
.Sh DESCRIPTION
.Nm
@ -81,6 +82,9 @@ Turns off all informational output the service generates.
Output from any non OpenRC comands is not affected.
.It Fl v , -verbose
Turns on any extra informational output the service generates.
.It Fl Z , -dry-run
Shows what services would be stopped and/or started without actually starting
or stopping them.
.El
.Pp
The following variables affect the service script:

View File

@ -75,31 +75,18 @@
#define WAIT_TIMEOUT 60 /* seconds until we timeout */
#define WARN_TIMEOUT 10 /* warn about this every N seconds */
static const char *applet = NULL;
static RC_STRINGLIST *applet_list = NULL;
static RC_STRINGLIST *restart_services = NULL;
static RC_STRINGLIST *need_services = NULL;
static RC_STRINGLIST *use_services = NULL;
static RC_STRINGLIST *services = NULL;
static RC_STRINGLIST *tmplist = NULL;
static char *service = NULL;
static int exclusive_fd = -1;
static RC_DEPTREE *deptree = NULL;
static char *runlevel = NULL;
static bool sighup = false;
static char *ibsave = NULL;
static bool in_background = false;
static RC_HOOK hook_out = 0;
static pid_t service_pid = 0;
static char *prefix = NULL;
static const char *applet;
static char *service, *runlevel, *ibsave, *prefix;;
static RC_DEPTREE *deptree;
static RC_STRINGLIST *applet_list, *services, *tmplist;
static RC_STRINGLIST *restart_services, *need_services, *use_services;
static RC_HOOK hook_out;
static int exclusive_fd = -1, master_tty = -1;
static bool sighup, in_background, deps, dry_run;
static pid_t service_pid;
static int signal_pipe[2] = { -1, -1 };
static int master_tty = -1;
static RC_STRINGLIST *types_b = NULL;
static RC_STRINGLIST *types_n = NULL;
static RC_STRINGLIST *types_nu = NULL;
static RC_STRINGLIST *types_nua = NULL;
static RC_STRINGLIST *types_m = NULL;
static RC_STRINGLIST *types_b, *types_n, *types_nu, *types_nua, *types_m;
static RC_STRINGLIST *types_mua = NULL;
#ifdef __linux__
@ -569,27 +556,17 @@ setup_types(void)
}
static void
svc_start(bool deps)
svc_start_check(void)
{
bool started;
bool background = false;
RC_STRING *svc;
RC_STRING *svc2;
int depoptions = RC_DEP_TRACE;
RC_SERVICE state;
bool first;
int n;
size_t len;
char *p;
char *tmp;
state = rc_service_state(service);
if (rc_yesno(getenv("IN_HOTPLUG")) || in_background) {
if (in_background) {
if (!(state & (RC_SERVICE_INACTIVE | RC_SERVICE_STOPPED)))
exit(EXIT_FAILURE);
background = true;
rc_service_mark(service, RC_SERVICE_HOTPLUGGED);
if (rc_yesno(getenv("IN_HOTPLUG")))
rc_service_mark(service, RC_SERVICE_HOTPLUGGED);
if (strcmp(runlevel, RC_LEVEL_SYSINIT) == 0)
ewarnx("WARNING: %s will be started in the"
" next runlevel", applet);
@ -611,148 +588,165 @@ svc_start(bool deps)
if (state & RC_SERVICE_STARTED) {
ewarn("WARNING: %s has already been started", applet);
return;
} else if (state & RC_SERVICE_INACTIVE && ! background)
} else if (state & RC_SERVICE_INACTIVE && !in_background)
ewarnx("WARNING: %s has already started, but is inactive",
applet);
rc_service_mark(service, RC_SERVICE_STARTING);
hook_out = RC_HOOK_SERVICE_START_OUT;
rc_plugin_run(RC_HOOK_SERVICE_START_IN, applet);
}
static void
svc_start_deps(void)
{
bool first;
RC_STRING *svc, *svc2;
RC_SERVICE state;
int depoptions = RC_DEP_TRACE, n;
size_t len;
char *p, *tmp;
pid_t pid;
errno = 0;
if (rc_conf_yesno("rc_depend_strict") || errno == ENOENT)
depoptions |= RC_DEP_STRICT;
if (deps) {
if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL))
eerrorx("failed to load deptree");
if (!types_b)
setup_types();
if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL))
eerrorx("failed to load deptree");
if (!types_b)
setup_types();
services = rc_deptree_depends(deptree, types_b, applet_list,
runlevel, 0);
if (TAILQ_FIRST(services)) {
eerrorn("ERROR: %s needs service(s) ", applet);
first = true;
TAILQ_FOREACH(svc, services, entries) {
if (first)
first = false;
else
fprintf(stderr, ", ");
fprintf(stderr, "%s", svc->value);
}
fprintf(stderr, "\n");
exit(EXIT_FAILURE);
}
rc_stringlist_free(services);
services = NULL;
need_services = rc_deptree_depends(deptree, types_n,
applet_list, runlevel,
depoptions);
use_services = rc_deptree_depends(deptree, types_nu,
applet_list, runlevel,
depoptions);
if (!rc_runlevel_starting()) {
TAILQ_FOREACH(svc, use_services, entries) {
state = rc_service_state(svc->value);
/* Don't stop failed services again.
* If you remove this check, ensure that the
* exclusive file isn't created. */
if (state & RC_SERVICE_FAILED &&
rc_runlevel_starting())
continue;
if (state & RC_SERVICE_STOPPED) {
pid_t pid = service_start(svc->value);
if (! rc_conf_yesno("rc_parallel"))
rc_waitpid(pid);
}
}
}
/* Now wait for them to start */
services = rc_deptree_depends(deptree, types_nua, applet_list,
runlevel, depoptions);
/* We use tmplist to hold our scheduled by list */
tmplist = rc_stringlist_new();
services = rc_deptree_depends(deptree, types_b, applet_list,
runlevel, 0);
if (TAILQ_FIRST(services)) {
eerrorn("ERROR: %s needs service(s) ", applet);
first = true;
TAILQ_FOREACH(svc, services, entries) {
state = rc_service_state(svc->value);
if (state & RC_SERVICE_STARTED)
continue;
if (first)
first = false;
else
fprintf(stderr, ", ");
fprintf(stderr, "%s", svc->value);
}
fprintf(stderr, "\n");
exit(EXIT_FAILURE);
}
rc_stringlist_free(services);
services = NULL;
/* Don't wait for services which went inactive but are
* now in starting state which we are after */
if (state & RC_SERVICE_STARTING &&
state & RC_SERVICE_WASINACTIVE)
{
if (!rc_stringlist_find(need_services,
svc->value) &&
!rc_stringlist_find(use_services,
svc->value))
need_services = rc_deptree_depends(deptree, types_n,
applet_list, runlevel, depoptions);
use_services = rc_deptree_depends(deptree, types_nu,
applet_list, runlevel, depoptions);
if (!rc_runlevel_starting()) {
TAILQ_FOREACH(svc, use_services, entries) {
state = rc_service_state(svc->value);
/* Don't stop failed services again.
* If you remove this check, ensure that the
* exclusive file isn't created. */
if (state & RC_SERVICE_FAILED &&
rc_runlevel_starting())
continue;
if (state & RC_SERVICE_STOPPED) {
if (dry_run) {
printf(" %s", svc->value);
continue;
}
if (!svc_wait(svc->value))
eerror("%s: timed out waiting for %s",
applet, svc->value);
state = rc_service_state(svc->value);
if (state & RC_SERVICE_STARTED)
continue;
if (rc_stringlist_find(need_services, svc->value)) {
if (state & RC_SERVICE_INACTIVE ||
state & RC_SERVICE_WASINACTIVE)
{
rc_stringlist_add(tmplist, svc->value);
} else if (!TAILQ_FIRST(tmplist))
eerrorx("ERROR: cannot start %s as"
" %s would not start",
applet, svc->value);
}
pid = service_start(svc->value);
if (!rc_conf_yesno("rc_parallel"))
rc_waitpid(pid);
}
}
if (TAILQ_FIRST(tmplist)) {
/* Set the state now, then unlink our exclusive so that
our scheduled list is preserved */
rc_service_mark(service, RC_SERVICE_STOPPED);
rc_stringlist_free(use_services);
use_services = NULL;
len = 0;
n = 0;
TAILQ_FOREACH(svc, tmplist, entries) {
rc_service_schedule_start(svc->value, service);
use_services = rc_deptree_depend(deptree,
"iprovide",
svc->value);
TAILQ_FOREACH(svc2, use_services, entries)
rc_service_schedule_start(svc2->value,
service);
rc_stringlist_free(use_services);
use_services = NULL;
len += strlen(svc->value) + 2;
n++;
}
len += 5;
tmp = p = xmalloc(sizeof(char) * len);
TAILQ_FOREACH(svc, tmplist, entries) {
if (p != tmp)
p += snprintf(p, len, ", ");
p += snprintf(p, len - (p - tmp),
"%s", svc->value);
}
rc_stringlist_free(tmplist);
tmplist = NULL;
ewarnx("WARNING: %s is scheduled to start when "
"%s has started", applet, tmp);
free(tmp);
}
rc_stringlist_free(services);
services = NULL;
}
if (dry_run)
return;
/* Now wait for them to start */
services = rc_deptree_depends(deptree, types_nua, applet_list,
runlevel, depoptions);
/* We use tmplist to hold our scheduled by list */
tmplist = rc_stringlist_new();
TAILQ_FOREACH(svc, services, entries) {
state = rc_service_state(svc->value);
if (state & RC_SERVICE_STARTED)
continue;
/* Don't wait for services which went inactive but are
* now in starting state which we are after */
if (state & RC_SERVICE_STARTING &&
state & RC_SERVICE_WASINACTIVE)
{
if (!rc_stringlist_find(need_services, svc->value) &&
!rc_stringlist_find(use_services, svc->value))
continue;
}
if (!svc_wait(svc->value))
eerror("%s: timed out waiting for %s",
applet, svc->value);
state = rc_service_state(svc->value);
if (state & RC_SERVICE_STARTED)
continue;
if (rc_stringlist_find(need_services, svc->value)) {
if (state & RC_SERVICE_INACTIVE ||
state & RC_SERVICE_WASINACTIVE)
{
rc_stringlist_add(tmplist, svc->value);
} else if (!TAILQ_FIRST(tmplist))
eerrorx("ERROR: cannot start %s as"
" %s would not start",
applet, svc->value);
}
}
if (TAILQ_FIRST(tmplist)) {
/* Set the state now, then unlink our exclusive so that
our scheduled list is preserved */
rc_service_mark(service, RC_SERVICE_STOPPED);
rc_stringlist_free(use_services);
use_services = NULL;
len = 0;
n = 0;
TAILQ_FOREACH(svc, tmplist, entries) {
rc_service_schedule_start(svc->value, service);
use_services = rc_deptree_depend(deptree,
"iprovide", svc->value);
TAILQ_FOREACH(svc2, use_services, entries)
rc_service_schedule_start(svc2->value, service);
rc_stringlist_free(use_services);
use_services = NULL;
len += strlen(svc->value) + 2;
n++;
}
len += 5;
tmp = p = xmalloc(sizeof(char) * len);
TAILQ_FOREACH(svc, tmplist, entries) {
if (p != tmp)
p += snprintf(p, len, ", ");
p += snprintf(p, len - (p - tmp),
"%s", svc->value);
}
rc_stringlist_free(tmplist);
tmplist = NULL;
ewarnx("WARNING: %s is scheduled to start when "
"%s has started", applet, tmp);
free(tmp);
}
rc_stringlist_free(services);
services = NULL;
}
static void svc_start_real()
{
bool started;
RC_STRING *svc, *svc2;
if (ibsave)
setenv("IN_BACKGROUND", ibsave, 1);
hook_out = RC_HOOK_SERVICE_START_DONE;
@ -803,35 +797,46 @@ svc_start(bool deps)
}
static void
svc_stop(bool deps)
svc_start(void)
{
bool stopped;
RC_SERVICE state = rc_service_state(service);
int depoptions = RC_DEP_TRACE;
RC_STRING *svc;
if (dry_run)
einfon("start:");
else
svc_start_check();
if (deps)
svc_start_deps();
if (dry_run)
printf(" %s\n", applet);
else
svc_start_real();
}
static void
svc_stop_check(RC_SERVICE *state)
{
*state = rc_service_state(service);
if (rc_runlevel_stopping() && state & RC_SERVICE_FAILED)
if (rc_runlevel_stopping() && *state & RC_SERVICE_FAILED)
exit(EXIT_FAILURE);
if (rc_yesno(getenv("IN_HOTPLUG")) || in_background)
if (!(state & RC_SERVICE_STARTED) &&
!(state & RC_SERVICE_INACTIVE))
exit (EXIT_FAILURE);
if (in_background &&
!(*state & RC_SERVICE_STARTED) &&
!(*state & RC_SERVICE_INACTIVE))
exit(EXIT_FAILURE);
if (exclusive_fd == -1)
exclusive_fd = svc_lock(applet);
if (exclusive_fd == -1) {
if (errno == EACCES)
eerrorx("%s: superuser access required", applet);
if (state & RC_SERVICE_STOPPING)
if (*state & RC_SERVICE_STOPPING)
ewarnx("WARNING: %s is already stopping", applet);
eerrorx("ERROR: %s has been stopped by something else", applet);
eerrorx("ERROR: %s stopped by something else", applet);
}
fcntl(exclusive_fd, F_SETFD,
fcntl(exclusive_fd, F_GETFD, 0) | FD_CLOEXEC);
if (state & RC_SERVICE_STOPPED) {
if (*state & RC_SERVICE_STOPPED) {
ewarn("WARNING: %s is already stopped", applet);
return;
}
@ -846,84 +851,104 @@ svc_stop(bool deps)
else if (rc_service_in_runlevel(service, RC_LEVEL_BOOT))
ewarn("WARNING: you are stopping a boot service");
}
}
if (deps && !(state & RC_SERVICE_WASINACTIVE)) {
errno = 0;
if (rc_conf_yesno("rc_depend_strict") || errno == ENOENT)
depoptions |= RC_DEP_STRICT;
static void
svc_stop_deps(RC_SERVICE state)
{
int depoptions = RC_DEP_TRACE;
RC_STRING *svc;
pid_t pid;
if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL))
eerrorx("failed to load deptree");
if (state & RC_SERVICE_WASINACTIVE)
return;
errno = 0;
if (rc_conf_yesno("rc_depend_strict") || errno == ENOENT)
depoptions |= RC_DEP_STRICT;
if (!types_m)
setup_types();
if (!deptree && ((deptree = _rc_deptree_load(0, NULL)) == NULL))
eerrorx("failed to load deptree");
services = rc_deptree_depends(deptree, types_m, applet_list,
runlevel, depoptions);
tmplist = rc_stringlist_new();
TAILQ_FOREACH_REVERSE(svc, services, rc_stringlist, entries) {
state = rc_service_state(svc->value);
/* Don't stop failed services again.
* If you remove this check, ensure that the
* exclusive file isn't created. */
if (state & RC_SERVICE_FAILED &&
rc_runlevel_stopping())
if (!types_m)
setup_types();
services = rc_deptree_depends(deptree, types_m, applet_list,
runlevel, depoptions);
tmplist = rc_stringlist_new();
TAILQ_FOREACH_REVERSE(svc, services, rc_stringlist, entries) {
state = rc_service_state(svc->value);
/* Don't stop failed services again.
* If you remove this check, ensure that the
* exclusive file isn't created. */
if (state & RC_SERVICE_FAILED &&
rc_runlevel_stopping())
continue;
if (state & RC_SERVICE_STARTED ||
state & RC_SERVICE_INACTIVE)
{
if (dry_run) {
printf(" %s", svc->value);
continue;
}
svc_wait(svc->value);
state = rc_service_state(svc->value);
if (state & RC_SERVICE_STARTED ||
state & RC_SERVICE_INACTIVE)
{
svc_wait(svc->value);
state = rc_service_state(svc->value);
if (state & RC_SERVICE_STARTED ||
state & RC_SERVICE_INACTIVE)
{
pid_t pid = service_stop(svc->value);
if (!rc_conf_yesno("rc_parallel"))
rc_waitpid(pid);
rc_stringlist_add(tmplist, svc->value);
}
pid = service_stop(svc->value);
if (!rc_conf_yesno("rc_parallel"))
rc_waitpid(pid);
rc_stringlist_add(tmplist, svc->value);
}
}
rc_stringlist_free(services);
services = NULL;
TAILQ_FOREACH(svc, tmplist, entries) {
if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
continue;
svc_wait(svc->value);
if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
continue;
if (rc_runlevel_stopping()) {
/* If shutting down, we should stop even
* if a dependant failed */
if (runlevel &&
(strcmp(runlevel,
RC_LEVEL_SHUTDOWN) == 0 ||
strcmp(runlevel,
RC_LEVEL_SINGLE) == 0))
continue;
rc_service_mark(service, RC_SERVICE_FAILED);
}
eerrorx("ERROR: cannot stop %s as %s "
"is still up", applet, svc->value);
}
rc_stringlist_free(tmplist);
tmplist = NULL;
/* We now wait for other services that may use us and are
* stopping. This is important when a runlevel stops */
services = rc_deptree_depends(deptree, types_mua, applet_list,
runlevel, depoptions);
TAILQ_FOREACH(svc, services, entries) {
if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
continue;
svc_wait(svc->value);
}
rc_stringlist_free(services);
services = NULL;
}
rc_stringlist_free(services);
services = NULL;
if (dry_run)
return;
TAILQ_FOREACH(svc, tmplist, entries) {
if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
continue;
svc_wait(svc->value);
if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
continue;
if (rc_runlevel_stopping()) {
/* If shutting down, we should stop even
* if a dependant failed */
if (runlevel &&
(strcmp(runlevel,
RC_LEVEL_SHUTDOWN) == 0 ||
strcmp(runlevel,
RC_LEVEL_SINGLE) == 0))
continue;
rc_service_mark(service, RC_SERVICE_FAILED);
}
eerrorx("ERROR: cannot stop %s as %s "
"is still up", applet, svc->value);
}
rc_stringlist_free(tmplist);
tmplist = NULL;
/* We now wait for other services that may use us and are
* stopping. This is important when a runlevel stops */
services = rc_deptree_depends(deptree, types_mua, applet_list,
runlevel, depoptions);
TAILQ_FOREACH(svc, services, entries) {
if (rc_service_state(svc->value) & RC_SERVICE_STOPPED)
continue;
svc_wait(svc->value);
}
rc_stringlist_free(services);
services = NULL;
}
static void
svc_stop_real(void)
{
bool stopped;
/* If we're stopping localmount, set LC_ALL=C so that
* bash doesn't load anything blocking the unmounting of /usr */
if (strcmp(applet, "localmount") == 0)
@ -952,7 +977,25 @@ svc_stop(bool deps)
}
static void
svc_restart(bool deps)
svc_stop(void)
{
RC_SERVICE state;
state = 0;
if (dry_run)
einfon("stop:");
else
svc_stop_check(&state);
if (deps)
svc_stop_deps(state);
if (dry_run)
printf(" %s\n", applet);
else
svc_stop_real();
}
static void
svc_restart(void)
{
/* This is hairly and a better way needs to be found I think!
* The issue is this - openvpn need net and dns. net can restart
@ -976,10 +1019,13 @@ svc_restart(bool deps)
if (!(rc_service_state(service) & RC_SERVICE_STOPPED)) {
get_started_services();
svc_stop(deps);
svc_stop();
if (dry_run)
ewarn("Cannot calculate restart start dependencies"
" on a dry-run");
}
svc_start(deps);
svc_start();
start_services(restart_services);
rc_stringlist_free(restart_services);
restart_services = NULL;
@ -1018,10 +1064,11 @@ service_plugable(void)
}
#include "_usage.h"
#define getoptstring "dDsvl:" getoptstring_COMMON
#define getoptstring "dDsvl:Z" getoptstring_COMMON
#define extraopts "stop | start | restart | describe | zap"
static const struct option longopts[] = {
{ "debug", 0, NULL, 'd'},
{ "dry-run", 0, NULL, 'Z'},
{ "ifstarted", 0, NULL, 's'},
{ "nodeps", 0, NULL, 'D'},
{ "lockfd", 1, NULL, 'l'},
@ -1029,6 +1076,7 @@ static const struct option longopts[] = {
};
static const char *const longopts_help[] = {
"set xtrace when running the script",
"show what would be done",
"only run commands when started",
"ignore dependencies",
"fd of the exclusive lock from rc",
@ -1039,19 +1087,12 @@ static const char *const longopts_help[] = {
int
runscript(int argc, char **argv)
{
bool deps = true;
bool doneone = false;
char pidstr[10];
int retval;
int opt;
int retval, opt, depoptions = RC_DEP_TRACE;
RC_STRING *svc;
char path[PATH_MAX];
char lnk[PATH_MAX];
size_t l = 0;
size_t ll;
char *dir, *save = NULL;
char path[PATH_MAX], lnk[PATH_MAX], *dir, *save = NULL, pidstr[10];
size_t l = 0, ll;
const char *file;
int depoptions = RC_DEP_TRACE;
struct stat stbuf;
/* Show help if insufficient args */
@ -1153,6 +1194,8 @@ runscript(int argc, char **argv)
setup_selinux(argc, argv);
#endif
deps = true;
/* Punt the first arg as it's our service name */
argc--;
argv++;
@ -1176,8 +1219,11 @@ runscript(int argc, char **argv)
case 'D':
deps = false;
break;
case_RC_COMMON_GETOPT
}
case 'Z':
dry_run = true;
break;
case_RC_COMMON_GETOPT;
}
/* If we're changing runlevels and not called by rc then we cannot
work with any dependencies */
@ -1196,6 +1242,7 @@ runscript(int argc, char **argv)
if (rc_yesno(getenv("IN_HOTPLUG"))) {
if (!service_plugable())
eerrorx("%s: not allowed to be hotplugged", applet);
in_background = true;
}
/* Setup a signal handler */
@ -1277,18 +1324,18 @@ runscript(int argc, char **argv)
{
if (rc_service_state(service) &
RC_SERVICE_STARTED)
svc_restart(deps);
svc_restart();
} else if (strcmp(optarg, "restart") == 0) {
svc_restart(deps);
svc_restart();
} else if (strcmp(optarg, "start") == 0) {
svc_start(deps);
svc_start();
} else if (strcmp(optarg, "stop") == 0) {
if (deps && in_background)
get_started_services();
svc_stop(deps);
svc_stop();
if (deps) {
if (! in_background &&
! rc_runlevel_stopping() &&
if (!in_background &&
!rc_runlevel_stopping() &&
rc_service_state(service) &
RC_SERVICE_STOPPED)
unhotplug();