diff options
Diffstat (limited to 'src/librc.c')
-rw-r--r-- | src/librc.c | 773 |
1 files changed, 773 insertions, 0 deletions
diff --git a/src/librc.c b/src/librc.c new file mode 100644 index 00000000..d9c4a539 --- /dev/null +++ b/src/librc.c @@ -0,0 +1,773 @@ +/* + librc + core RC functions + Copyright 2007 Gentoo Foundation + Released under the GPLv2 + */ + +#include <sys/types.h> +#include <sys/select.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <errno.h> +#ifndef __linux__ +/* Although linux should work fine, gcc likes to bitch with our default + CFLAGS so we just don't include the file and use the GNU one defined + in string.h */ +#include <libgen.h> +#endif +#include <limits.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "einfo.h" +#include "rc.h" +#include "rc-misc.h" +#include "strlist.h" + +/* usecs to wait while we poll the fifo */ +#define WAIT_INTERVAL 20000 + +/* max secs to wait until a service comes up */ +#define WAIT_MAX 60 + +#define SOFTLEVEL RC_SVCDIR "softlevel" + +static const char *rc_service_state_names[] = { + "started", + "stopped", + "starting", + "stopping", + "inactive", + "wasinactive", + "coldplugged", + "failed", + NULL +}; + +bool rc_runlevel_starting (void) +{ + return (rc_is_dir (RC_SVCDIR "softscripts.old")); +} + +bool rc_runlevel_stopping (void) +{ + return (rc_is_dir (RC_SVCDIR "softscripts.new")); +} + +char **rc_get_runlevels (void) +{ + char **dirs = rc_ls_dir (NULL, RC_RUNLEVELDIR, 0); + char **runlevels = NULL; + int i; + char *dir; + + STRLIST_FOREACH (dirs, dir, i) + { + char *path = rc_strcatpaths (RC_RUNLEVELDIR, dir, NULL); + if (rc_is_dir (path)) + runlevels = rc_strlist_addsort (runlevels, dir); + free (path); + } + rc_strlist_free (dirs); + + return (runlevels); +} + +char *rc_get_runlevel (void) +{ + FILE *fp; + static char buffer [PATH_MAX]; + + if (! (fp = fopen (SOFTLEVEL, "r"))) + { + strcpy (buffer, "sysinit"); + return (buffer); + } + + if (fgets (buffer, PATH_MAX, fp)) + { + int i = strlen (buffer) - 1; + if (buffer[i] == '\n') + buffer[i] = 0; + fclose (fp); + return (buffer); + } + + fclose (fp); + strcpy (buffer, "sysinit"); + return (buffer); +} + +void rc_set_runlevel (const char *runlevel) +{ + FILE *fp = fopen (SOFTLEVEL, "w"); + if (! fp) + eerrorx ("failed to open `" SOFTLEVEL "': %s", strerror (errno)); + fprintf (fp, "%s", runlevel); + fclose (fp); +} + +bool rc_runlevel_exists (const char *runlevel) +{ + char *path; + bool retval; + + if (! runlevel) + return (false); + + path = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, NULL); + retval = rc_is_dir (path); + free (path); + return (retval); +} + +/* Resolve a service name to it's full path */ +char *rc_resolve_service (const char *service) +{ + char buffer[PATH_MAX]; + char *file; + int r = 0; + + if (! service) + return (NULL); + + if (service[0] == '/') + return (strdup (service)); + + file = rc_strcatpaths (RC_SVCDIR, "started", service, NULL); + if (! rc_is_link (file)) + { + free (file); + file = rc_strcatpaths (RC_SVCDIR, "inactive", service, NULL); + if (! rc_is_link (file)) + { + free (file); + file = NULL; + } + } + + memset (buffer, 0, sizeof (buffer)); + if (file) + { + r = readlink (file, buffer, sizeof (buffer)); + free (file); + if (r > 0) + return strdup (buffer); + } + + snprintf (buffer, sizeof (buffer), RC_INITDIR "%s", service); + return (strdup (buffer)); +} + +bool rc_service_exists (const char *service) +{ + char *file; + bool retval = false; + int len; + + if (! service) + return (false); + + len = strlen (service); + + /* .sh files are not init scripts */ + if (len > 2 && service[len - 3] == '.' && + service[len - 2] == 's' && + service[len - 1] == 'h') + return (false); + + file = rc_resolve_service (service); + if (rc_exists (file)) + retval = rc_is_exec (file); + free (file); + return (retval); +} + +bool rc_service_in_runlevel (const char *service, const char *runlevel) +{ + char *file; + bool retval; + + if (! runlevel || ! service) + return (false); + + if (! rc_service_exists (service)) + return (false); + + file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename (service), NULL); + retval = rc_exists (file); + free (file); + + return (retval); +} + +bool rc_mark_service (const char *service, const rc_service_state_t state) +{ + char *file; + int i = 0; + int skip_state = -1; + char *base; + char *init = rc_resolve_service (service); + bool skip_wasinactive = false; + + if (! service) + return (false); + + base = basename (service); + + if (state != rc_service_stopped) + { + if (! rc_is_file(init)) + { + free (init); + return (false); + } + + file = rc_strcatpaths (RC_SVCDIR, rc_service_state_names[state], base, NULL); + if (rc_exists (file)) + unlink (file); + i = symlink (init, file); + if (i != 0) + { + free (file); + free (init); + einfo ("%d %s %s", state, rc_service_state_names[state], base); + eerror ("symlink `%s' to `%s': %s", init, file, strerror (errno)); + return (false); + } + + free (file); + skip_state = state; + } + + if (state == rc_service_coldplugged) + { + free (init); + return (true); + } + + /* Remove any old states now */ + i = 0; + while (rc_service_state_names[i]) + { + if ((i != skip_state && + i != rc_service_stopped && + i != rc_service_coldplugged && + i != rc_service_crashed) && + (! skip_wasinactive || i != rc_service_wasinactive)) + { + file = rc_strcatpaths (RC_SVCDIR, rc_service_state_names[i], base, NULL); + if (rc_exists (file)) + { + if ((state == rc_service_starting || + state == rc_service_stopping) && + i == rc_service_inactive) + { + char *wasfile = rc_strcatpaths (RC_SVCDIR, + rc_service_state_names[rc_service_wasinactive], + base, NULL); + + if (symlink (init, wasfile) != 0) + eerror ("symlink `%s' to `%s': %s", init, wasfile, + strerror (errno)); + + skip_wasinactive = true; + free (wasfile); + } + + errno = 0; + if (unlink (file) != 0 && errno != ENOENT) + eerror ("failed to delete `%s': %s", file, + strerror (errno)); + } + free (file); + } + i++; + } + + /* Remove the exclusive state if we're inactive */ + if (state == rc_service_started || + state == rc_service_stopped || + state == rc_service_inactive) + { + file = rc_strcatpaths (RC_SVCDIR, "exclusive", base, NULL); + if (rc_exists (file)) + if (unlink (file) != 0) + eerror ("unlink `%s': %s", file, strerror (errno)); + free (file); + } + + /* Remove any options and daemons the service may have stored */ + if (state == rc_service_stopped) + { + char *dir = rc_strcatpaths (RC_SVCDIR, "options", base, NULL); + + if (rc_is_dir (dir)) + rc_rm_dir (dir, true); + free (dir); + + dir = rc_strcatpaths (RC_SVCDIR, "daemons", base, NULL); + if (rc_is_dir (dir)) + rc_rm_dir (dir, true); + free (dir); + + rc_schedule_clear (service); + } + + /* These are final states, so remove us from scheduled */ + if (state == rc_service_started || state == rc_service_stopped) + { + char *sdir = rc_strcatpaths (RC_SVCDIR, "scheduled", NULL); + char **dirs = rc_ls_dir (NULL, sdir, 0); + char *dir; + int serrno; + + STRLIST_FOREACH (dirs, dir, i) + { + char *bdir = rc_strcatpaths (sdir, dir, NULL); + file = rc_strcatpaths (bdir, base, NULL); + if (rc_exists (file)) + if (unlink (file) != 0) + eerror ("unlink `%s': %s", file, strerror (errno)); + free (file); + + /* Try and remove the dir - we don't care about errors */ + serrno = errno; + rmdir (bdir); + errno = serrno; + free (bdir); + } + rc_strlist_free (dirs); + free (sdir); + } + + free (init); + return (true); +} + +bool rc_service_state (const char *service, const rc_service_state_t state) +{ + char *file; + bool retval; + + /* If the init script does not exist then we are stopped */ + if (! rc_service_exists (service)) + return (state == rc_service_stopped ? true : false); + + /* We check stopped state by not being in any of the others */ + if (state == rc_service_stopped) + return ( ! (rc_service_state (service, rc_service_started) || + rc_service_state (service, rc_service_starting) || + rc_service_state (service, rc_service_stopping) || + rc_service_state (service, rc_service_inactive))); + + /* The crashed state and scheduled states are virtual */ + if (state == rc_service_crashed) + return (rc_service_daemons_crashed (service)); + else if (state == rc_service_scheduled) + { + char **services = rc_services_scheduled_by (service); + retval = (services); + if (services) + free (services); + return (retval); + } + + /* Now we just check if a file by the service name rc_exists + in the state dir */ + file = rc_strcatpaths (RC_SVCDIR, rc_service_state_names[state], + basename (service), NULL); + retval = rc_exists (file); + free (file); + return (retval); +} + +bool rc_get_service_option (const char *service, const char *option, + char *value) +{ + FILE *fp; + char buffer[1024]; + char *file = rc_strcatpaths (RC_SVCDIR, "options", service, option, NULL); + bool retval = false; + + if (rc_exists (file)) + { + if ((fp = fopen (file, "r")) == NULL) + eerror ("fopen `%s': %s", file, strerror (errno)); + else + { + memset (buffer, 0, sizeof (buffer)); + while (fgets (buffer, RC_LINEBUFFER, fp)) + { + memcpy (value, buffer, strlen (buffer)); + value += strlen (buffer); + } + fclose (fp); + retval = true; + } + } + + free (file); + return (retval); +} + +bool rc_set_service_option (const char *service, const char *option, + const char *value) +{ + FILE *fp; + char *path = rc_strcatpaths (RC_SVCDIR, "options", service, NULL); + char *file = rc_strcatpaths (path, option, NULL); + bool retval = false; + + if (! rc_is_dir (path)) + { + if (mkdir (path, 0755) != 0) + { + eerror ("mkdir `%s': %s", path, strerror (errno)); + free (path); + free (file); + return (false); + } + } + + if ((fp = fopen (file, "w")) == NULL) + eerror ("fopen `%s': %s", file, strerror (errno)); + else + { + if (value) + fprintf (fp, "%s", value); + fclose (fp); + retval = true; + } + + free (path); + free (file); + return (retval); +} + +static pid_t _exec_service (const char *service, const char *arg) +{ + char *file; + char *fifo; + pid_t pid = -1; + pid_t savedpid; + int status; + + file = rc_resolve_service (service); + if (! rc_is_file (file)) + { + rc_mark_service (service, rc_service_stopped); + free (file); + return (0); + } + + /* We create a fifo so that other services can wait until we complete */ + fifo = rc_strcatpaths (RC_SVCDIR, "exclusive", basename (service), NULL); + + if (mkfifo (fifo, 0600) != 0 && errno != EEXIST) + { + eerror ("unable to create fifo `%s': %s", fifo, strerror (errno)); + free (fifo); + free (file); + return (-1); + } + + if ((pid = fork ()) == 0) + { + char *myarg = strdup (arg); + int e = 0; + execl (file, file, myarg, NULL); + e = errno; + free (myarg); + unlink (fifo); + free (fifo); + eerrorx ("unable to exec `%s': %s", file, strerror (errno)); + } + + free (fifo); + free (file); + + if (pid == -1) + { + eerror ("unable to fork: %s", strerror (errno)); + return (pid); + } + + if (rc_is_env ("RC_PARALLEL_STARTUP", "yes")) + return (pid); + + savedpid = pid; + errno = 0; + do + { + pid = waitpid (savedpid, &status, 0); + if (pid < 0) + { + if (errno != ECHILD) + eerror ("waitpid %d: %s", savedpid, strerror (errno)); + return (-1); + } + } while (! WIFEXITED (status) && ! WIFSIGNALED (status)); + + return (0); +} + +pid_t rc_stop_service (const char *service) +{ + if (rc_service_state (service, rc_service_stopped)) + return (0); + + return (_exec_service (service, "stop")); +} + + +pid_t rc_start_service (const char *service) +{ + if (! rc_service_state (service, rc_service_stopped)) + return (0); + + return (_exec_service (service, "start")); +} + +void rc_schedule_start_service (const char *service, + const char *service_to_start) +{ + char *dir; + char *init; + char *file; + + if (! rc_service_exists (service) || ! rc_service_exists (service_to_start)) + return; + + dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename (service), NULL); + if (! rc_is_dir (dir)) + if (mkdir (dir, 0755) != 0) + { + eerror ("mkdir `%s': %s", dir, strerror (errno)); + free (dir); + return; + } + + init = rc_resolve_service (service_to_start); + file = rc_strcatpaths (dir, basename (service_to_start), NULL); + if (! rc_exists (file) && symlink (init, file) != 0) + eerror ("symlink `%s' to `%s': %s", init, file, strerror (errno)); + + free (init); + free (file); + free (dir); +} + +void rc_schedule_clear (const char *service) +{ + char *dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename (service), NULL); + + if (rc_is_dir (dir)) + rc_rm_dir (dir, true); + free (dir); +} + +bool rc_wait_service (const char *service) +{ + char *fifo = rc_strcatpaths (RC_SVCDIR, "exclusive", basename (service), NULL); + struct timeval tv; + struct timeval stopat; + struct timeval now; + bool retval = false; + + if (gettimeofday (&stopat, NULL) != 0) + { + eerror ("gettimeofday: %s", strerror (errno)); + return (false); + } + stopat.tv_sec += WAIT_MAX; + + while (true) + { + if (! rc_exists (fifo)) + { + retval = true; + break; + } + + tv.tv_sec = 0; + tv.tv_usec = WAIT_INTERVAL; + if (select (0, 0, 0, 0, &tv) < 0) + { + if (errno != EINTR) + eerror ("select: %s",strerror (errno)); + break; + } + + /* Don't hang around forever */ + if (gettimeofday (&now, NULL) != 0) + { + eerror ("gettimeofday: %s", strerror (errno)); + break; + } + if (timercmp (&now, &stopat, >)) + break; + } + + free (fifo); + return (retval); +} + +char **rc_services_in_runlevel (const char *runlevel) +{ + char *dir; + char **list = NULL; + + if (! runlevel) + return (rc_ls_dir (NULL, RC_INITDIR, RC_LS_INITD)); + + /* These special levels never contain any services */ + if (strcmp (runlevel, RC_LEVEL_SYSINIT) == 0 || + strcmp (runlevel, RC_LEVEL_SINGLE) == 0) + return (NULL); + + dir = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, NULL); + if (! rc_is_dir (dir)) + eerror ("runlevel `%s' does not exist", runlevel); + else + list = rc_ls_dir (list, dir, RC_LS_INITD); + + free (dir); + return (list); +} + +char **rc_services_in_state (rc_service_state_t state) +{ + char *dir = rc_strcatpaths (RC_SVCDIR, rc_service_state_names[state], NULL); + char **list = NULL; + + if (rc_is_dir (dir)) + list = rc_ls_dir (list, dir, RC_LS_INITD); + + free (dir); + return (list); +} + +bool rc_service_add (const char *runlevel, const char *service) +{ + bool retval; + char *init; + char *file; + + if (! rc_runlevel_exists (runlevel)) + { + errno = ENOENT; + return (false); + } + + if (rc_service_in_runlevel (service, runlevel)) + { + errno = EEXIST; + return (false); + } + + init = rc_resolve_service (service); + file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename (service), NULL); + retval = (symlink (init, file) == 0); + free (init); + free (file); + return (retval); +} + +bool rc_service_delete (const char *runlevel, const char *service) +{ + char *file; + bool retval = false; + + if (! runlevel || ! service) + return (false); + + file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename (service), NULL); + if (unlink (file) == 0) + retval = true; + + free (file); + return (retval); +} + +char **rc_services_scheduled_by (const char *service) +{ + char **dirs = rc_ls_dir (NULL, RC_SVCDIR "scheduled", 0); + char **list = NULL; + char *dir; + int i; + + STRLIST_FOREACH (dirs, dir, i) + { + char *file = rc_strcatpaths (RC_SVCDIR "scheduled", dir, service, NULL); + if (rc_exists (file)) + list = rc_strlist_add (list, file); + free (file); + } + rc_strlist_free (dirs); + + return (list); +} + +char **rc_services_scheduled (const char *service) +{ + char *dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename (service), NULL); + char **list = NULL; + + if (rc_is_dir (dir)) + list = rc_ls_dir (list, dir, RC_LS_INITD); + + free (dir); + return (list); +} + +bool rc_allow_plug (char *service) +{ + char *list; + char *p; + char *star; + char *token; + bool allow = true; + char *match = getenv ("RC_PLUG_SERVICES"); + if (! match) + return true; + + list = strdup (match); + p = list; + while ((token = strsep (&p, " "))) + { + bool truefalse = true; + if (token[0] == '!') + { + truefalse = false; + token++; + } + + star = strchr (token, '*'); + if (star) + { + if (strncmp (service, token, star - token) == 0) + { + allow = truefalse; + break; + } + } + else + { + if (strcmp (service, token) == 0) + { + allow = truefalse; + break; + } + } + } + + free (list); + return (allow); +} |