diff options
Diffstat (limited to 'src/librc/librc.c')
-rw-r--r-- | src/librc/librc.c | 891 |
1 files changed, 891 insertions, 0 deletions
diff --git a/src/librc/librc.c b/src/librc/librc.c new file mode 100644 index 00000000..15309f87 --- /dev/null +++ b/src/librc/librc.c @@ -0,0 +1,891 @@ +/* + librc + core RC functions + */ + +/* + * Copyright 2007-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +const char librc_copyright[] = "Copyright (c) 2007-2008 Roy Marples"; + +#include "librc.h" + +#define SOFTLEVEL RC_SVCDIR "/softlevel" + +#ifndef S_IXUGO +# define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH) +#endif + +/* File stream used for plugins to write environ vars to */ +FILE *rc_environ_fd = NULL; + +typedef struct rc_service_state_name { + rc_service_state_t state; + const char *name; +} rc_service_state_name_t; + +/* We MUST list the states below 0x10 first + * The rest can be in any order */ +static const rc_service_state_name_t rc_service_state_names[] = { + { RC_SERVICE_STARTED, "started" }, + { RC_SERVICE_STOPPED, "stopped" }, + { RC_SERVICE_STARTING, "starting" }, + { RC_SERVICE_STOPPING, "stopping" }, + { RC_SERVICE_INACTIVE, "inactive" }, + { RC_SERVICE_WASINACTIVE, "wasinactive" }, + { RC_SERVICE_COLDPLUGGED, "coldplugged" }, + { RC_SERVICE_FAILED, "failed" }, + { RC_SERVICE_SCHEDULED, "scheduled"}, + { 0, NULL} +}; + +#define LS_INITD 0x01 +#define LS_DIR 0x02 +static char **ls_dir (const char *dir, int options) +{ + DIR *dp; + struct dirent *d; + char **list = NULL; + struct stat buf; + + if ((dp = opendir (dir)) == NULL) + return (NULL); + + while (((d = readdir (dp)) != NULL)) { + if (d->d_name[0] != '.') { + if (options & LS_INITD) { + int l = strlen (d->d_name); + + /* Check that our file really exists. + * This is important as a service maybe in a runlevel, but + * could also have been removed. */ + char *file = rc_strcatpaths (dir, d->d_name, NULL); + int ok = stat (file, &buf); + free (file); + if (ok != 0) + continue; + + /* .sh files are not init scripts */ + if (l > 2 && d->d_name[l - 3] == '.' && + d->d_name[l - 2] == 's' && + d->d_name[l - 1] == 'h') + continue; + } + if (options & LS_DIR) { + if (stat (d->d_name, &buf) == 0 && ! S_ISDIR (buf.st_mode)) + continue; + } + rc_strlist_addsort (&list, d->d_name); + } + } + closedir (dp); + + return (list); +} + +static bool rm_dir (const char *pathname, bool top) +{ + DIR *dp; + struct dirent *d; + + if ((dp = opendir (pathname)) == NULL) + return (false); + + errno = 0; + while (((d = readdir (dp)) != NULL) && errno == 0) { + if (strcmp (d->d_name, ".") != 0 && strcmp (d->d_name, "..") != 0) { + char *tmp = rc_strcatpaths (pathname, d->d_name, (char *) NULL); + if (d->d_type == DT_DIR) { + if (! rm_dir (tmp, true)) + { + free (tmp); + closedir (dp); + return (false); + } + } else { + if (unlink (tmp)) { + free (tmp); + closedir (dp); + return (false); + } + } + free (tmp); + } + } + closedir (dp); + + if (top && rmdir (pathname) != 0) + return (false); + + return (true); +} + +static const char *rc_parse_service_state (rc_service_state_t state) +{ + int i; + + for (i = 0; rc_service_state_names[i].name; i++) { + if (rc_service_state_names[i].state == state) + return (rc_service_state_names[i].name); + } + + return (NULL); +} + +bool rc_runlevel_starting (void) +{ + return (exists (RC_STARTING)); +} +librc_hidden_def(rc_runlevel_starting) + +bool rc_runlevel_stopping (void) +{ + return (exists (RC_STOPPING)); +} +librc_hidden_def(rc_runlevel_stopping) + +char **rc_runlevel_list (void) +{ + return (ls_dir (RC_RUNLEVELDIR, LS_DIR)); +} +librc_hidden_def(rc_runlevel_list) + +char *rc_runlevel_get (void) +{ + FILE *fp; + char *runlevel = NULL; + + if ((fp = fopen (SOFTLEVEL, "r"))) { + runlevel = xmalloc (sizeof (char) * PATH_MAX); + if (fgets (runlevel, PATH_MAX, fp)) { + int i = strlen (runlevel) - 1; + if (runlevel[i] == '\n') + runlevel[i] = 0; + } else + *runlevel = '\0'; + fclose (fp); + } + + if (! runlevel || ! *runlevel) { + free (runlevel); + runlevel = xstrdup (RC_LEVEL_SYSINIT); + } + + return (runlevel); +} +librc_hidden_def(rc_runlevel_get) + +bool rc_runlevel_set (const char *runlevel) +{ + FILE *fp = fopen (SOFTLEVEL, "w"); + + if (! fp) + return (false); + fprintf (fp, "%s", runlevel); + fclose (fp); + return (true); +} +librc_hidden_def(rc_runlevel_set) + +bool rc_runlevel_exists (const char *runlevel) +{ + char *path; + struct stat buf; + bool retval = false; + + if (! runlevel) + return (false); + + path = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, (char *) NULL); + if (stat (path, &buf) == 0 && S_ISDIR (buf.st_mode)) + retval = true; + free (path); + return (retval); +} +librc_hidden_def(rc_runlevel_exists) + +/* Resolve a service name to it's full path */ +char *rc_service_resolve (const char *service) +{ + char buffer[PATH_MAX]; + char *file; + int r = 0; + struct stat buf; + + if (! service) + return (NULL); + + if (service[0] == '/') + return (xstrdup (service)); + + file = rc_strcatpaths (RC_SVCDIR, "started", service, (char *) NULL); + if (lstat (file, &buf) || ! S_ISLNK (buf.st_mode)) { + free (file); + file = rc_strcatpaths (RC_SVCDIR, "inactive", service, (char *) NULL); + if (lstat (file, &buf) || ! S_ISLNK (buf.st_mode)) { + free (file); + file = NULL; + } + } + + memset (buffer, 0, sizeof (buffer)); + if (file) { + r = readlink (file, buffer, sizeof (buffer)); + free (file); + if (r > 0) + return (xstrdup (buffer)); + } + snprintf (buffer, sizeof (buffer), RC_INITDIR "/%s", service); + + /* So we don't exist in /etc/init.d - check /usr/local/etc/init.d */ + if (stat (buffer, &buf) != 0) { + snprintf (buffer, sizeof (buffer), RC_INITDIR_LOCAL "/%s", service); + if (stat (buffer, &buf) != 0) + return (NULL); + } + + return (xstrdup (buffer)); +} +librc_hidden_def(rc_service_resolve) + +bool rc_service_exists (const char *service) +{ + char *file; + bool retval = false; + int len; + struct stat buf; + + 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_service_resolve (service); + if (stat (file, &buf) == 0 && buf.st_mode & S_IXUGO) + retval = true; + free (file); + return (retval); +} +librc_hidden_def(rc_service_exists) + +#define OPTSTR ". '%s'; echo \"${opts}\"" +char **rc_service_extra_commands (const char *service) +{ + char *svc; + char *cmd = NULL; + char *buffer = NULL; + char **commands = NULL; + char *token; + char *p = buffer; + FILE *fp; + int l; + + if (! (svc = rc_service_resolve (service))) + return (NULL); + + l = strlen (OPTSTR) + strlen (svc) + 1; + cmd = xmalloc (sizeof (char) * l); + snprintf (cmd, l, OPTSTR, svc); + free (svc); + if ((fp = popen (cmd, "r"))) { + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + if (fgets (buffer, RC_LINEBUFFER, fp)) { + if (buffer[strlen (buffer) - 1] == '\n') + buffer[strlen (buffer) - 1] = '\0'; + while ((token = strsep (&p, " "))) + rc_strlist_addsort (&commands, token); + } + pclose (fp); + free (buffer); + } + free (cmd); + return (commands); +} +librc_hidden_def(rc_service_extra_commands) + +#define DESCSTR ". '%s'; echo \"${description%s%s}\"" +char *rc_service_description (const char *service, const char *option) +{ + char *svc; + char *cmd = NULL; + char *buffer; + char *desc = NULL; + FILE *fp; + int i; + int l; + + if (! (svc = rc_service_resolve (service))) + return (NULL); + + if (! option) + option = ""; + + l = strlen (DESCSTR) + strlen (svc) + strlen (option) + 2; + cmd = xmalloc (sizeof (char) * l); + snprintf (cmd, l, DESCSTR, svc, option ? "_" : "", option); + free (svc); + if ((fp = popen (cmd, "r"))) { + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + while (fgets (buffer, RC_LINEBUFFER, fp)) { + if (! desc) { + desc = xmalloc (strlen (buffer) + 1); + *desc = '\0'; + } else { + desc = xrealloc (desc, strlen (desc) + strlen (buffer) + 1); + } + i = strlen (desc); + memcpy (desc + i, buffer, strlen (buffer)); + memset (desc + i + strlen (buffer), 0, 1); + } + free (buffer); + pclose (fp); + } + free (cmd); + return (desc); +} +librc_hidden_def(rc_service_description) + +bool rc_service_in_runlevel (const char *service, const char *runlevel) +{ + char *file; + bool retval; + + if (! runlevel || ! service) + return (false); + + file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename_c (service), + (char *) NULL); + retval = exists (file); + free (file); + + return (retval); +} +librc_hidden_def(rc_service_in_runlevel) + +bool rc_service_mark (const char *service, const rc_service_state_t state) +{ + char *file; + int i = 0; + int skip_state = -1; + const char *base; + char *init = rc_service_resolve (service); + bool skip_wasinactive = false; + + if (! init) + return (false); + + base = basename_c (service); + + if (state != RC_SERVICE_STOPPED) { + if (! exists (init)) { + free (init); + return (false); + } + + file = rc_strcatpaths (RC_SVCDIR, rc_parse_service_state (state), base, + (char *) NULL); + if (exists (file)) + unlink (file); + i = symlink (init, file); + if (i != 0) { + free (file); + free (init); + return (false); + } + + free (file); + skip_state = state; + } + + if (state == RC_SERVICE_COLDPLUGGED || state == RC_SERVICE_FAILED) { + free (init); + return (true); + } + + /* Remove any old states now */ + for (i = 0; rc_service_state_names[i].name; i++) { + int s = rc_service_state_names[i].state; + + if ((s != skip_state && + s != RC_SERVICE_STOPPED && + s != RC_SERVICE_COLDPLUGGED && + s != RC_SERVICE_SCHEDULED) && + (! skip_wasinactive || s != RC_SERVICE_WASINACTIVE)) + { + file = rc_strcatpaths (RC_SVCDIR, rc_parse_service_state (s), base, + (char *) NULL); + if (exists (file)) { + if ((state == RC_SERVICE_STARTING || + state == RC_SERVICE_STOPPING) && + s == RC_SERVICE_INACTIVE) + { + char *wasfile = rc_strcatpaths (RC_SVCDIR, + rc_parse_service_state (RC_SERVICE_WASINACTIVE), + base, (char *) NULL); + + symlink (init, wasfile); + skip_wasinactive = true; + free (wasfile); + } + unlink (file); + } + free (file); + } + } + + /* 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, (char *) NULL); + unlink (file); + 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, (char *) NULL); + rm_dir (dir, true); + free (dir); + + dir = rc_strcatpaths (RC_SVCDIR, "daemons", base, (char *) NULL); + rm_dir (dir, true); + free (dir); + + rc_service_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", (char *) NULL); + char **dirs = ls_dir (sdir, 0); + char *dir; + int serrno; + + STRLIST_FOREACH (dirs, dir, i) { + char *bdir = rc_strcatpaths (sdir, dir, (char *) NULL); + file = rc_strcatpaths (bdir, base, (char *) NULL); + unlink (file); + 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); +} +librc_hidden_def(rc_service_mark) + +rc_service_state_t rc_service_state (const char *service) +{ + int i; + int state = RC_SERVICE_STOPPED; + + for (i = 0; rc_service_state_names[i].name; i++) { + char *file = rc_strcatpaths (RC_SVCDIR, rc_service_state_names[i].name, + basename_c (service), (char*) NULL); + if (exists (file)) { + if (rc_service_state_names[i].state <= 0x10) + state = rc_service_state_names[i].state; + else + state |= rc_service_state_names[i].state; + } + free (file); + } + + if (state & RC_SERVICE_STOPPED) { + char **services = rc_services_scheduled_by (service); + if (services) { + state |= RC_SERVICE_SCHEDULED; + free (services); + } + } + + return (state); +} +librc_hidden_def(rc_service_state) + +char *rc_service_value_get (const char *service, const char *option) +{ + FILE *fp; + char *buffer = NULL; + char *file = rc_strcatpaths (RC_SVCDIR, "options", service, option, + (char *) NULL); + + if ((fp = fopen (file, "r"))) { + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + fgets (buffer, RC_LINEBUFFER, fp); + fclose (fp); + } + free (file); + + return (buffer); +} +librc_hidden_def(rc_service_value_get) + +bool rc_service_value_set (const char *service, const char *option, + const char *value) +{ + FILE *fp; + char *path = rc_strcatpaths (RC_SVCDIR, "options", service, (char *) NULL); + char *file = rc_strcatpaths (path, option, (char *) NULL); + bool retval = false; + + if (mkdir (path, 0755) != 0 && errno != EEXIST) { + free (path); + free (file); + return (false); + } + + if ((fp = fopen (file, "w"))) { + if (value) + fprintf (fp, "%s", value); + fclose (fp); + retval = true; + } + + free (path); + free (file); + return (retval); +} +librc_hidden_def(rc_service_value_set) + +static pid_t _exec_service (const char *service, const char *arg) +{ + char *file; + char *fifo; + pid_t pid = -1; + + file = rc_service_resolve (service); + if (! exists (file)) { + rc_service_mark (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_c (service), + (char *) NULL); + + if (mkfifo (fifo, 0600) != 0 && errno != EEXIST) { + free (fifo); + free (file); + return (-1); + } + + if ((pid = vfork ()) == 0) { + execl (file, file, arg, (char *) NULL); + fprintf (stderr, "unable to exec `%s': %s\n", file, strerror (errno)); + unlink (fifo); + _exit (EXIT_FAILURE); + } + + free (fifo); + free (file); + + if (pid == -1) + fprintf (stderr, "vfork: %s\n", strerror (errno)); + + return (pid); +} + +pid_t rc_service_stop (const char *service) +{ + rc_service_state_t state = rc_service_state (service); + + if (state & RC_SERVICE_FAILED) + return (-1); + + if (state & RC_SERVICE_STOPPED) + return (0); + + return (_exec_service (service, "stop")); +} +librc_hidden_def(rc_service_stop) + +pid_t rc_service_start (const char *service) +{ + rc_service_state_t state = rc_service_state (service); + + if (state & RC_SERVICE_FAILED) + return (-1); + + if (! state & RC_SERVICE_STOPPED) + return (0); + + return (_exec_service (service, "start")); +} +librc_hidden_def(rc_service_start) + +bool rc_service_schedule_start (const char *service, + const char *service_to_start) +{ + char *dir; + char *init; + char *file; + bool retval; + + /* service may be a provided service, like net */ + if (! service || ! rc_service_exists (service_to_start)) + return (false); + + dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename_c (service), + (char *) NULL); + if (mkdir (dir, 0755) != 0 && errno != EEXIST) { + free (dir); + return (false); + } + + init = rc_service_resolve (service_to_start); + file = rc_strcatpaths (dir, basename_c (service_to_start), (char *) NULL); + retval = (exists (file) || symlink (init, file) == 0); + free (init); + free (file); + free (dir); + + return (retval); +} +librc_hidden_def(rc_service_schedule_start) + +bool rc_service_schedule_clear (const char *service) +{ + char *dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename_c (service), + (char *) NULL); + bool retval; + + if (! (retval = rm_dir (dir, true)) && errno == ENOENT) + retval = true; + free (dir); + return (retval); +} +librc_hidden_def(rc_service_schedule_clear) + + +char **rc_services_in_runlevel (const char *runlevel) +{ + char *dir; + char **list = NULL; + + if (! runlevel) { + int i; + char **local = ls_dir (RC_INITDIR_LOCAL, LS_INITD); + + list = ls_dir (RC_INITDIR, LS_INITD); + STRLIST_FOREACH (local, dir, i) + rc_strlist_addsortu (&list, dir); + rc_strlist_free (local); + return (list); + } + + /* 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, (char *) NULL); + list = ls_dir (dir, LS_INITD); + free (dir); + return (list); +} +librc_hidden_def(rc_services_in_runlevel) + +char **rc_services_in_state (rc_service_state_t state) +{ + char *dir = rc_strcatpaths (RC_SVCDIR, rc_parse_service_state (state), + (char *) NULL); + char **list = NULL; + + if (state == RC_SERVICE_SCHEDULED) { + char **dirs = ls_dir (dir, 0); + char *d; + int i; + + STRLIST_FOREACH (dirs, d, i) { + char *p = rc_strcatpaths (dir, d, (char *) NULL); + char **entries = ls_dir (p, LS_INITD); + char *e; + int j; + + STRLIST_FOREACH (entries, e, j) + rc_strlist_addsortu (&list, e); + + if (entries) + free (entries); + } + + if (dirs) + free (dirs); + } else { + list = ls_dir (dir, LS_INITD); + } + + free (dir); + return (list); +} +librc_hidden_def(rc_services_in_state) + +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_service_resolve (service); + + /* We need to ensure that only things in /etc/init.d are added + * to the boot runlevel */ + if (strcmp (runlevel, RC_LEVEL_BOOT) == 0) { + char *tmp = xstrdup (init); + retval = (strcmp (dirname (tmp), RC_INITDIR) == 0); + free (tmp); + if (! retval) { + free (init); + errno = EPERM; + return (false); + } + } + + file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename_c (service), + (char *) NULL); + retval = (symlink (init, file) == 0); + free (init); + free (file); + return (retval); +} +librc_hidden_def(rc_service_add) + +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_c (service), + (char *) NULL); + if (unlink (file) == 0) + retval = true; + + free (file); + return (retval); +} +librc_hidden_def(rc_service_delete) + +char **rc_services_scheduled_by (const char *service) +{ + char **dirs = ls_dir (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, + (char *) NULL); + if (exists (file)) + rc_strlist_add (&list, file); + free (file); + } + rc_strlist_free (dirs); + + return (list); +} +librc_hidden_def(rc_services_scheduled_by) + +char **rc_services_scheduled (const char *service) +{ + char *dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename_c (service), + (char *) NULL); + char **list = NULL; + + list = ls_dir (dir, LS_INITD); + free (dir); + return (list); +} +librc_hidden_def(rc_services_scheduled) + +bool rc_service_plugable (const char *service) +{ + char *list; + char *p; + char *star; + char *token; + bool allow = true; + char *match = getenv ("RC_PLUG_SERVICES"); + if (! match) + return true; + + list = xstrdup (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); +} +librc_hidden_def(rc_service_plugable) |