summaryrefslogtreecommitdiff
path: root/src/librc/librc.c
diff options
context:
space:
mode:
authorRoy Marples <roy@marples.name>2008-01-05 19:25:55 +0000
committerRoy Marples <roy@marples.name>2008-01-05 19:25:55 +0000
commitac21d75300dabe83578e4373fcfd09d67c3a083b (patch)
treed5f8e2a16920add2277c79ff8a1b7e99ec2976df /src/librc/librc.c
parent112fbde453d55c49b7999d2e35496a8758aaa7b5 (diff)
downloadopenrc-ac21d75300dabe83578e4373fcfd09d67c3a083b.tar.gz
openrc-ac21d75300dabe83578e4373fcfd09d67c3a083b.tar.bz2
openrc-ac21d75300dabe83578e4373fcfd09d67c3a083b.tar.xz
Add some .mk stubs to impersonate bsk .mk files to make writing our Makefiles easier. libeinfo, librc and rc now have their own seperate directories. More work is needed to tidy this up though.
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 0000000..15309f8
--- /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)