aboutsummaryrefslogtreecommitdiff
path: root/src/librc/librc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/librc/librc.c')
-rw-r--r--src/librc/librc.c891
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)