summaryrefslogtreecommitdiff
path: root/src/rc
diff options
context:
space:
mode:
Diffstat (limited to 'src/rc')
-rw-r--r--src/rc/_usage.c55
-rw-r--r--src/rc/_usage.h53
-rw-r--r--src/rc/builtins.h39
-rw-r--r--src/rc/checkpath.c233
-rw-r--r--src/rc/fstabinfo.c235
-rw-r--r--src/rc/mountinfo.c464
-rw-r--r--src/rc/rc-depend.c192
-rw-r--r--src/rc/rc-logger.c258
-rw-r--r--src/rc/rc-logger.h32
-rw-r--r--src/rc/rc-misc.c353
-rw-r--r--src/rc/rc-plugin.c241
-rw-r--r--src/rc/rc-plugin.h55
-rw-r--r--src/rc/rc-status.c197
-rw-r--r--src/rc/rc-update.c272
-rw-r--r--src/rc/rc.c1574
-rw-r--r--src/rc/rc.map58
-rw-r--r--src/rc/runscript.c1311
-rw-r--r--src/rc/start-stop-daemon.c1070
-rw-r--r--src/rc/start-stop-daemon.pam6
19 files changed, 6698 insertions, 0 deletions
diff --git a/src/rc/_usage.c b/src/rc/_usage.c
new file mode 100644
index 0000000..b079e32
--- /dev/null
+++ b/src/rc/_usage.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2007 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.
+ */
+
+__attribute__ ((__noreturn__))
+static void usage (int exit_status)
+{
+ const char * const has_arg[] = { "", "<arg>", "[arg]" };
+ int i;
+
+ printf ("Usage: %s [options] ", applet);
+#ifdef extraopts
+ printf (extraopts);
+#endif
+ printf ("\n\nOptions: [" getoptstring "]\n");
+ for (i = 0; longopts[i].name; ++i) {
+ int len = printf (" -%c, --%s %s", longopts[i].val, longopts[i].name,
+ has_arg[longopts[i].has_arg]);
+
+ char *lo = xstrdup (longopts_help[i]);
+ char *p = lo;
+ char *token;
+
+ while ((token = strsep (&p, "\n"))) {
+ while (++len < 37)
+ printf (" ");
+ puts (token);
+ len = 0;
+ }
+ free (lo);
+ }
+ exit (exit_status);
+}
diff --git a/src/rc/_usage.h b/src/rc/_usage.h
new file mode 100644
index 0000000..e4a5fa3
--- /dev/null
+++ b/src/rc/_usage.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2007 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.
+ */
+
+#define getoptstring_COMMON "Chqv"
+
+#define longopts_COMMON \
+ { "help", 0, NULL, 'h'}, \
+ { "nocolor", 0, NULL, 'C'}, \
+ { "verbose", 0, NULL, 'v'}, \
+ { "quiet", 0, NULL, 'q'}, \
+ { NULL, 0, NULL, 0 }
+
+#define longopts_help_COMMON \
+ "Display this help output", \
+ "Disable color output", \
+ "Run verbosely", \
+ "Run quietly"
+
+#define case_RC_COMMON_getopt_case_C setenv ("EINFO_COLOR", "NO", 1);
+#define case_RC_COMMON_getopt_case_h usage (EXIT_SUCCESS);
+#define case_RC_COMMON_getopt_case_v setenv ("EINFO_VERBOSE", "YES", 1);
+#define case_RC_COMMON_getopt_case_q setenv ("EINFO_QUIET", "YES", 1);
+#define case_RC_COMMON_getopt_default usage (EXIT_FAILURE);
+
+#define case_RC_COMMON_GETOPT \
+ case 'C': case_RC_COMMON_getopt_case_C; break; \
+ case 'h': case_RC_COMMON_getopt_case_h; break; \
+ case 'v': case_RC_COMMON_getopt_case_v; break; \
+ case 'q': case_RC_COMMON_getopt_case_q; break; \
+ default: case_RC_COMMON_getopt_default; break;
diff --git a/src/rc/builtins.h b/src/rc/builtins.h
new file mode 100644
index 0000000..43a4d9f
--- /dev/null
+++ b/src/rc/builtins.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2007 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.
+ */
+
+#include "rc.h"
+
+int checkpath (int argc, char **argv);
+int fstabinfo (int argc, char **argv);
+int mountinfo (int argc, char **argv);
+int rc_depend (int argc, char **argv);
+int rc_status (int argc, char **argv);
+int rc_update (int argc, char **argv);
+int runscript (int argc, char **argv);
+int start_stop_daemon (int argc, char **argv);
+
+/* Handy function so we can wrap einfo around our deptree */
+rc_depinfo_t *_rc_deptree_load (int *regen);
diff --git a/src/rc/checkpath.c b/src/rc/checkpath.c
new file mode 100644
index 0000000..612a076
--- /dev/null
+++ b/src/rc/checkpath.c
@@ -0,0 +1,233 @@
+/*
+ checkpath.c
+ Checks for the existance of a file or directory and creates it
+ if necessary. It can also correct its ownership.
+ */
+
+/*
+ * Copyright 2007 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.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "builtins.h"
+#include "einfo.h"
+#include "rc-misc.h"
+
+static const char *applet;
+
+static int do_check (char *path, uid_t uid, gid_t gid, mode_t mode, int file)
+{
+ struct stat st;
+
+ memset (&st, 0, sizeof (struct stat));
+
+ if (stat (path, &st)) {
+ if (file) {
+ int fd;
+ einfo ("%s: creating file", path);
+ if ((fd = open (path, O_CREAT)) == -1) {
+ eerror ("%s: open: %s", applet, strerror (errno));
+ return (-1);
+ }
+ close (fd);
+ } else {
+ einfo ("%s: creating directory", path);
+ if (! mode)
+ mode = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH;
+ if (mkdir (path, mode)) {
+ eerror ("%s: mkdir: %s", applet, strerror (errno));
+ return (-1);
+ }
+ mode = 0;
+ }
+ } else {
+ if ((file && S_ISDIR (st.st_mode)) ||
+ (! file && ! S_ISDIR (st.st_mode)))
+ {
+ if (file)
+ eerror ("%s: is a directory", path);
+ else
+ eerror ("%s: is a file", path);
+ return (-1);
+ }
+ }
+
+ if (mode && (st.st_mode & 0777) != mode) {
+ einfo ("%s: correcting mode", applet);
+ if (chmod (path, mode)) {
+ eerror ("%s: chmod: %s", applet, strerror (errno));
+ return (-1);
+ }
+ }
+
+ if (st.st_uid != uid || st.st_gid != gid) {
+ if (st.st_dev || st.st_ino)
+ einfo ("%s: correcting owner", path);
+ if (chown (path, uid, gid)) {
+ eerror ("%s: chown: %s", applet, strerror (errno));
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+/* Based on busybox */
+static int parse_mode (mode_t *mode, char *text)
+{
+ /* Check for a numeric mode */
+ if ((*mode - '0') < 8) {
+ char *p;
+ unsigned long l = strtoul (text, &p, 8);
+ if (*p || l > 07777U) {
+ errno = EINVAL;
+ return (-1);
+ }
+ *mode = l;
+ return (0);
+ }
+
+ /* We currently don't check g+w type stuff */
+ errno = EINVAL;
+ return (-1);
+}
+
+static int parse_owner (struct passwd **user, struct group **group,
+ const char *owner)
+{
+ char *u = xstrdup (owner);
+ char *g = strchr (u, ':');
+ int id = 0;
+ int retval = 0;
+
+ if (g)
+ *g++ = '\0';
+
+ if (user && *u) {
+ if (sscanf (u, "%d", &id) == 1)
+ *user = getpwuid (id);
+ else
+ *user = getpwnam (u);
+ if (! *user)
+ retval = -1;
+ }
+
+ if (group && g && *g) {
+ if (sscanf (g, "%d", &id) == 1)
+ *group = getgrgid (id);
+ else
+ *group = getgrnam (g);
+ if (! *group)
+ retval = -1;
+ }
+
+ free (u);
+ return (retval);
+}
+
+#include "_usage.h"
+#define extraopts "dir1 dir2 ..."
+#define getoptstring "fm:o:" getoptstring_COMMON
+static struct option longopts[] = {
+ { "directory", 0, NULL, 'd'},
+ { "file", 0, NULL, 'f'},
+ { "mode", 1, NULL, 'm'},
+ { "owner", 1, NULL, 'o'},
+ longopts_COMMON
+};
+static const char * const longopts_help[] = {
+ "Check if a directory",
+ "Check if a file",
+ "Mode to check",
+ "Owner to check (user:group)",
+ longopts_help_COMMON
+};
+#include "_usage.c"
+
+int checkpath (int argc, char **argv)
+{
+ int opt;
+ uid_t uid = geteuid();
+ gid_t gid = getgid();
+ mode_t mode = 0;
+ struct passwd *pw = NULL;
+ struct group *gr = NULL;
+ bool file = 0;
+ int retval = EXIT_SUCCESS;
+
+ applet = basename_c (argv[0]);
+
+ while ((opt = getopt_long (argc, argv, getoptstring,
+ longopts, (int *) 0)) != -1)
+ {
+ switch (opt) {
+ case 'd':
+ file = 0;
+ break;
+ case 'f':
+ file = 1;
+ break;
+ case 'm':
+ if (parse_mode (&mode, optarg) != 0)
+ eerrorx ("%s: invalid mode `%s'", applet, optarg);
+ break;
+ case 'o':
+ if (parse_owner (&pw, &gr, optarg) != 0)
+ eerrorx ("%s: owner `%s' not found", applet, optarg);
+ break;
+
+ case_RC_COMMON_GETOPT
+ }
+ }
+
+ if (optind >= argc)
+ usage (EXIT_FAILURE);
+
+ if (pw) {
+ uid = pw->pw_uid;
+ gid = pw->pw_gid;
+ }
+ if (gr)
+ gid = gr->gr_gid;
+
+ while (optind < argc) {
+ if (do_check (argv[optind], uid, gid, mode, file))
+ retval = EXIT_FAILURE;
+ optind++;
+ }
+
+ exit (retval);
+}
diff --git a/src/rc/fstabinfo.c b/src/rc/fstabinfo.c
new file mode 100644
index 0000000..5f8e469
--- /dev/null
+++ b/src/rc/fstabinfo.c
@@ -0,0 +1,235 @@
+/*
+ fstabinfo.c
+ Gets information about /etc/fstab.
+ */
+
+/*
+ * Copyright 2007 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.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Yay for linux and it's non liking of POSIX functions.
+ Okay, we could use getfsent but the man page says use getmntent instead
+ AND we don't have getfsent on uclibc or dietlibc for some odd reason. */
+#ifdef __linux__
+#define HAVE_GETMNTENT
+#include <mntent.h>
+#define START_ENT fp = setmntent ("/etc/fstab", "r");
+#define GET_ENT getmntent (fp)
+#define GET_ENT_FILE(_name) getmntfile (_name)
+#define END_ENT endmntent (fp)
+#define ENT_BLOCKDEVICE(_ent) ent->mnt_fsname
+#define ENT_FILE(_ent) ent->mnt_dir
+#define ENT_TYPE(_ent) ent->mnt_type
+#define ENT_OPTS(_ent) ent->mnt_opts
+#define ENT_PASS(_ent) ent->mnt_passno
+#else
+#define HAVE_GETFSENT
+#include <fstab.h>
+#define START_ENT
+#define GET_ENT getfsent ()
+#define GET_ENT_FILE(_name) getfsfile (_name)
+#define END_ENT endfsent ()
+#define ENT_BLOCKDEVICE(_ent) ent->fs_spec
+#define ENT_TYPE(_ent) ent->fs_vfstype
+#define ENT_FILE(_ent) ent->fs_file
+#define ENT_OPTS(_ent) ent->fs_mntops
+#define ENT_PASS(_ent) ent->fs_passno
+#endif
+
+#include "builtins.h"
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "strlist.h"
+
+#ifdef HAVE_GETMNTENT
+static struct mntent *getmntfile (const char *file)
+{
+ struct mntent *ent = NULL;
+ FILE *fp;
+
+ START_ENT;
+ while ((ent = getmntent (fp)))
+ if (strcmp (file, ent->mnt_dir) == 0)
+ break;
+ END_ENT;
+
+ return (ent);
+}
+#endif
+
+static const char *applet = NULL;
+
+#include "_usage.h"
+#define getoptstring "bmop:t:" getoptstring_COMMON
+static struct option longopts[] = {
+ { "blockdevice", 0, NULL, 'b' },
+ { "options", 0, NULL, 'o' },
+ { "passno", 1, NULL, 'p' },
+ { "fstype", 1, NULL, 't' },
+ longopts_COMMON
+};
+static const char * const longopts_help[] = {
+ "Extract the block device",
+ "Extract the options field",
+ "Extract or query the pass number field",
+ "List entries with matching file system type",
+ longopts_help_COMMON
+};
+#include "_usage.c"
+
+#define OUTPUT_FILE (1 << 1)
+#define OUTPUT_OPTIONS (1 << 3)
+#define OUTPUT_PASSNO (1 << 4)
+#define OUTPUT_BLOCKDEV (1 << 5)
+
+int fstabinfo (int argc, char **argv)
+{
+#ifdef HAVE_GETMNTENT
+ FILE *fp;
+ struct mntent *ent;
+#else
+ struct fstab *ent;
+#endif
+ int result = EXIT_SUCCESS;
+ char *token;
+ int i;
+ int opt;
+ int output = OUTPUT_FILE;
+ char **files = NULL;
+ char *file;
+ bool filtered = false;
+
+ applet = basename_c (argv[0]);
+
+ /* Ensure that we are only quiet when explicitly told to be */
+ unsetenv ("EINFO_QUIET");
+
+ while ((opt = getopt_long (argc, argv, getoptstring,
+ longopts, (int *) 0)) != -1)
+ {
+ switch (opt) {
+ case 'b':
+ output = OUTPUT_BLOCKDEV;
+ break;
+ case 'o':
+ output = OUTPUT_OPTIONS;
+ break;
+
+ case 'p':
+ switch (optarg[0]) {
+ case '=':
+ case '<':
+ case '>':
+ if (sscanf (optarg + 1, "%d", &i) != 1)
+ eerrorx ("%s: invalid passno %s", argv[0], optarg + 1);
+
+ filtered = true;
+ START_ENT;
+ while ((ent = GET_ENT)) {
+ if (((optarg[0] == '=' && i == ENT_PASS (ent)) ||
+ (optarg[0] == '<' && i > ENT_PASS (ent)) ||
+ (optarg[0] == '>' && i < ENT_PASS (ent))) &&
+ strcmp (ENT_FILE (ent), "none") != 0)
+ rc_strlist_add (&files, ENT_FILE (ent));
+ }
+ END_ENT;
+ break;
+
+ default:
+ rc_strlist_add (&files, optarg);
+ output = OUTPUT_PASSNO;
+ break;
+ }
+ break;
+
+ case 't':
+ filtered = true;
+ while ((token = strsep (&optarg, ","))) {
+ START_ENT;
+ while ((ent = GET_ENT))
+ if (strcmp (token, ENT_TYPE (ent)) == 0)
+ rc_strlist_add (&files, ENT_FILE (ent));
+ END_ENT;
+ }
+ break;
+
+ case_RC_COMMON_GETOPT
+ }
+ }
+
+ while (optind < argc)
+ rc_strlist_add (&files, argv[optind++]);
+
+ if (! files && ! filtered) {
+ START_ENT;
+ while ((ent = GET_ENT))
+ rc_strlist_add (&files, ENT_FILE (ent));
+ END_ENT;
+
+ if (! files)
+ eerrorx ("%s: emtpy fstab", argv[0]);
+ }
+
+ /* Ensure we always display something */
+ START_ENT;
+ STRLIST_FOREACH (files, file, i) {
+ if (! (ent = GET_ENT_FILE (file))) {
+ result = EXIT_FAILURE;
+ continue;
+ }
+
+ /* No point in outputting if quiet */
+ if (rc_yesno (getenv ("EINFO_QUIET")))
+ continue;
+
+ switch (output) {
+ case OUTPUT_BLOCKDEV:
+ printf ("%s\n", ENT_BLOCKDEVICE (ent));
+ break;
+ case OUTPUT_OPTIONS:
+ printf ("%s\n", ENT_OPTS (ent));
+ break;
+
+ case OUTPUT_FILE:
+ printf ("%s\n", file);
+ break;
+
+ case OUTPUT_PASSNO:
+ printf ("%d\n", ENT_PASS (ent));
+ break;
+ }
+ }
+ END_ENT;
+
+ rc_strlist_free (files);
+ exit (result);
+}
diff --git a/src/rc/mountinfo.c b/src/rc/mountinfo.c
new file mode 100644
index 0000000..05ce8dd
--- /dev/null
+++ b/src/rc/mountinfo.c
@@ -0,0 +1,464 @@
+/*
+ mountinfo.c
+ Obtains information about mounted filesystems.
+ */
+
+/*
+ * Copyright 2007 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.
+ */
+
+#include <sys/types.h>
+
+#if defined(__DragonFly__) || defined(__FreeBSD__) || \
+ defined(__NetBSD__) || defined(__OpenBSD__)
+#define BSD
+#include <sys/param.h>
+#include <sys/ucred.h>
+#include <sys/mount.h>
+#elif defined (__linux__)
+#include <mntent.h>
+#endif
+
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <regex.h>
+
+#include "builtins.h"
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "strlist.h"
+
+static const char *applet;
+
+typedef enum {
+ mount_from,
+ mount_to,
+ mount_fstype,
+ mount_options
+} mount_type;
+
+typedef enum {
+ net_ignore,
+ net_yes,
+ net_no
+} net_opts;
+
+struct args {
+ regex_t *node_regex;
+ regex_t *skip_node_regex;
+ regex_t *fstype_regex;
+ regex_t *skip_fstype_regex;
+ regex_t *options_regex;
+ regex_t *skip_options_regex;
+ char **mounts;
+ mount_type mount_type;
+ net_opts netdev;
+};
+
+static int process_mount (char ***list, struct args *args,
+ char *from, char *to, char *fstype, char *options,
+ int netdev)
+{
+ char *p;
+
+ errno = ENOENT;
+
+#ifdef __linux__
+ /* Skip the really silly rootfs */
+ if (strcmp (fstype, "rootfs") == 0)
+ return (-1);
+#endif
+
+ if (args->netdev == net_yes && (netdev != -1 || args->mounts)) {
+ if (netdev != 0)
+ return (1);
+ } else if (args->netdev == net_no && (netdev != -1 || args->mounts)) {
+ if (netdev != 1)
+ return (1);
+ } else {
+ if (args->node_regex &&
+ regexec (args->node_regex, from, 0, NULL, 0) != 0)
+ return (1);
+ if (args->skip_node_regex &&
+ regexec (args->skip_node_regex, from, 0, NULL, 0) == 0)
+ return (1);
+
+ if (args->fstype_regex &&
+ regexec (args->fstype_regex, fstype, 0, NULL, 0) != 0)
+ return (-1);
+ if (args->skip_fstype_regex &&
+ regexec (args->skip_fstype_regex, fstype, 0, NULL, 0) == 0)
+ return (-1);
+
+ if (args->options_regex &&
+ regexec (args->options_regex, options, 0, NULL, 0) != 0)
+ return (-1);
+ if (args->skip_options_regex &&
+ regexec (args->skip_options_regex, options, 0, NULL, 0) == 0)
+ return (-1);
+ }
+
+ if (args->mounts) {
+ bool found = false;
+ int j;
+ char *mnt;
+ STRLIST_FOREACH (args->mounts, mnt, j)
+ if (strcmp (mnt, to) == 0) {
+ found = true;
+ break;
+ }
+ if (! found)
+ return (-1);
+ }
+
+ switch (args->mount_type) {
+ case mount_from:
+ p = from;
+ break;
+ case mount_to:
+ p = to;
+ break;
+ case mount_fstype:
+ p = fstype;
+ break;
+ case mount_options:
+ p = options;
+ break;
+ default:
+ p = NULL;
+ errno = EINVAL;
+ break;
+ }
+
+ if (p) {
+ errno = 0;
+ rc_strlist_addsortc (list, p);
+ return (0);
+ }
+
+ return (-1);
+}
+
+#ifdef BSD
+
+/* Translate the mounted options to english
+ * This is taken directly from FreeBSD mount.c */
+static struct opt {
+ int o_opt;
+ const char *o_name;
+} optnames[] = {
+ { MNT_ASYNC, "asynchronous" },
+ { MNT_EXPORTED, "NFS exported" },
+ { MNT_LOCAL, "local" },
+ { MNT_NOATIME, "noatime" },
+ { MNT_NOEXEC, "noexec" },
+ { MNT_NOSUID, "nosuid" },
+ { MNT_NOSYMFOLLOW, "nosymfollow" },
+ { MNT_QUOTA, "with quotas" },
+ { MNT_RDONLY, "read-only" },
+ { MNT_SYNCHRONOUS, "synchronous" },
+ { MNT_UNION, "union" },
+ { MNT_NOCLUSTERR, "noclusterr" },
+ { MNT_NOCLUSTERW, "noclusterw" },
+ { MNT_SUIDDIR, "suiddir" },
+ { MNT_SOFTDEP, "soft-updates" },
+ { MNT_MULTILABEL, "multilabel" },
+ { MNT_ACLS, "acls" },
+#ifdef MNT_GJOURNAL
+ { MNT_GJOURNAL, "gjournal" },
+#endif
+ { 0, NULL }
+};
+
+static char **find_mounts (struct args *args)
+{
+ struct statfs *mnts;
+ int nmnts;
+ int i;
+ char **list = NULL;
+ char *options = NULL;
+ int flags;
+ struct opt *o;
+
+ if ((nmnts = getmntinfo (&mnts, MNT_NOWAIT)) == 0)
+ eerrorx ("getmntinfo: %s", strerror (errno));
+
+ for (i = 0; i < nmnts; i++) {
+ int netdev = 0;
+ flags = mnts[i].f_flags & MNT_VISFLAGMASK;
+ for (o = optnames; flags && o->o_opt; o++) {
+ if (flags & o->o_opt) {
+ if (o->o_opt == MNT_LOCAL)
+ netdev = 1;
+ if (! options)
+ options = xstrdup (o->o_name);
+ else {
+ char *tmp = NULL;
+ int l = strlen (options) + strlen (o->o_name) + 2;
+ tmp = xmalloc (sizeof (char) * l);
+ snprintf (tmp, l, "%s,%s", options, o->o_name);
+ free (options);
+ options = tmp;
+ }
+ }
+ flags &= ~o->o_opt;
+ }
+
+ process_mount (&list, args,
+ mnts[i].f_mntfromname,
+ mnts[i].f_mntonname,
+ mnts[i].f_fstypename,
+ options,
+ netdev);
+
+ free (options);
+ options = NULL;
+ }
+
+ return (list);
+}
+
+#elif defined (__linux__)
+static struct mntent *getmntfile (const char *file)
+{
+ struct mntent *ent = NULL;
+ FILE *fp;
+
+ fp = setmntent ("/etc/fstab", "r");
+ while ((ent = getmntent (fp)))
+ if (strcmp (file, ent->mnt_dir) == 0)
+ break;
+ endmntent (fp);
+
+ return (ent);
+}
+
+static char **find_mounts (struct args *args)
+{
+ FILE *fp;
+ char *buffer;
+ char *p;
+ char *from;
+ char *to;
+ char *fst;
+ char *opts;
+ char **list = NULL;
+ struct mntent *ent;
+ int netdev;
+
+ if ((fp = fopen ("/proc/mounts", "r")) == NULL)
+ eerrorx ("getmntinfo: %s", strerror (errno));
+
+ buffer = xmalloc (sizeof (char) * PATH_MAX * 3);
+ while (fgets (buffer, PATH_MAX * 3, fp)) {
+ netdev = -1;
+ p = buffer;
+ from = strsep (&p, " ");
+ to = strsep (&p, " ");
+ fst = strsep (&p, " ");
+ opts = strsep (&p, " ");
+
+ if ((ent = getmntfile (to))) {
+ if (strstr (ent->mnt_opts, "_netdev"))
+ netdev = 0;
+ }
+
+ process_mount (&list, args, from, to, fst, opts, netdev);
+ }
+ free (buffer);
+ fclose (fp);
+
+ return (list);
+}
+
+#else
+# error "Operating system not supported!"
+#endif
+
+static regex_t *get_regex (const char *string)
+{
+ regex_t *reg = xmalloc (sizeof (regex_t));
+ int result;
+ char buffer[256];
+
+ if ((result = regcomp (reg, string, REG_EXTENDED | REG_NOSUB)) != 0)
+ {
+ regerror (result, reg, buffer, sizeof (buffer));
+ eerrorx ("%s: invalid regex `%s'", applet, buffer);
+ }
+
+ return (reg);
+}
+
+#include "_usage.h"
+#define extraopts "[mount1] [mount2] ..."
+#define getoptstring "f:F:n:N:o:O:p:P:ist" getoptstring_COMMON
+static struct option longopts[] = {
+ { "fstype-regex", 1, NULL, 'f'},
+ { "skip-fstype-regex", 1, NULL, 'F'},
+ { "node-regex", 1, NULL, 'n'},
+ { "skip-node-regex", 1, NULL, 'N'},
+ { "options-regex", 1, NULL, 'o'},
+ { "skip-options-regex", 1, NULL, 'O'},
+ { "point-regex", 1, NULL, 'p'},
+ { "skip-point-regex", 1, NULL, 'P'},
+ { "options", 0, NULL, 'i'},
+ { "fstype", 0, NULL, 's'},
+ { "node", 0, NULL, 't'},
+ { "netdev", 0, NULL, 'e'},
+ { "nonetdev", 0, NULL, 'E'},
+ longopts_COMMON
+};
+static const char * const longopts_help[] = {
+ "fstype regex to find",
+ "fstype regex to skip",
+ "node regex to find",
+ "node regex to skip",
+ "options regex to find",
+ "options regex to skip",
+ "point regex to find",
+ "point regex to skip",
+ "print options",
+ "print fstype",
+ "print node",
+ "is it a network device",
+ "is it not a network device",
+ longopts_help_COMMON
+};
+#include "_usage.c"
+
+int mountinfo (int argc, char **argv)
+{
+ int i;
+ struct args args;
+ regex_t *point_regex = NULL;
+ regex_t *skip_point_regex = NULL;
+ char **nodes = NULL;
+ char *n;
+ int opt;
+ int result;
+ bool quiet;
+
+ /* Ensure that we are only quiet when explicitly told to be */
+ unsetenv ("EINFO_QUIET");
+
+#define DO_REG(_var) \
+ if (_var) free (_var); \
+ _var = get_regex (optarg);
+#define REG_FREE(_var) \
+ if (_var) { regfree (_var); free (_var); }
+
+ memset (&args, 0, sizeof (struct args));
+ args.mount_type = mount_to;
+ args.netdev = net_ignore;
+
+ while ((opt = getopt_long (argc, argv, getoptstring,
+ longopts, (int *) 0)) != -1)
+ {
+ switch (opt) {
+ case 'e':
+ args.netdev = net_yes;
+ break;
+ case 'E':
+ args.netdev = net_no;
+ break;
+ case 'f':
+ DO_REG (args.fstype_regex);
+ break;
+ case 'F':
+ DO_REG (args.skip_fstype_regex);
+ break;
+ case 'n':
+ DO_REG (args.node_regex);
+ break;
+ case 'N':
+ DO_REG (args.skip_node_regex);
+ break;
+ case 'o':
+ DO_REG (args.options_regex);
+ break;
+ case 'O':
+ DO_REG (args.skip_options_regex);
+ break;
+ case 'p':
+ DO_REG (point_regex);
+ break;
+ case 'P':
+ DO_REG (skip_point_regex);
+ break;
+ case 'i':
+ args.mount_type = mount_options;
+ break;
+ case 's':
+ args.mount_type = mount_fstype;
+ break;
+ case 't':
+ args.mount_type = mount_from;
+ break;
+
+ case_RC_COMMON_GETOPT
+ }
+ }
+
+ while (optind < argc) {
+ if (argv[optind][0] != '/')
+ eerrorx ("%s: `%s' is not a mount point", argv[0], argv[optind]);
+ rc_strlist_add (&args.mounts, argv[optind++]);
+ }
+
+ nodes = find_mounts (&args);
+
+ REG_FREE (args.fstype_regex);
+ REG_FREE (args.skip_fstype_regex);
+ REG_FREE (args.node_regex);
+ REG_FREE (args.skip_node_regex);
+ REG_FREE (args.options_regex);
+ REG_FREE (args.skip_options_regex);
+
+ rc_strlist_reverse (nodes);
+
+ result = EXIT_FAILURE;
+ quiet = rc_yesno (getenv ("EINFO_QUIET"));
+ STRLIST_FOREACH (nodes, n, i) {
+ if (point_regex && regexec (point_regex, n, 0, NULL, 0) != 0)
+ continue;
+ if (skip_point_regex && regexec (skip_point_regex, n, 0, NULL, 0) == 0)
+ continue;
+ if (! quiet)
+ printf ("%s\n", n);
+ result = EXIT_SUCCESS;
+ }
+ rc_strlist_free (nodes);
+
+ REG_FREE (point_regex);
+ REG_FREE (skip_point_regex);
+
+ exit (result);
+}
diff --git a/src/rc/rc-depend.c b/src/rc/rc-depend.c
new file mode 100644
index 0000000..c5e9e66
--- /dev/null
+++ b/src/rc/rc-depend.c
@@ -0,0 +1,192 @@
+/*
+ rc-depend
+ rc service dependency and ordering
+ */
+
+/*
+ * Copyright 2007 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.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <getopt.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "builtins.h"
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "strlist.h"
+
+static const char *applet;
+
+rc_depinfo_t *_rc_deptree_load (int *regen) {
+ if (rc_deptree_update_needed ()) {
+ int retval;
+
+ if (regen)
+ *regen = 1;
+
+ ebegin ("Caching service dependencies");
+ retval = rc_deptree_update ();
+ eend (retval ? 0 : -1, "Failed to update the dependency tree");
+ }
+
+ return (rc_deptree_load ());
+}
+
+#include "_usage.h"
+#define getoptstring "t:suT" getoptstring_COMMON
+static struct option longopts[] = {
+ { "type", 1, NULL, 't'},
+ { "notrace", 0, NULL, 'T'},
+ { "strict", 0, NULL, 's'},
+ { "update", 0, NULL, 'u'},
+ longopts_COMMON
+};
+static const char * const longopts_help[] = {
+ "Type(s) of dependency to list",
+ "Don't trace service dependencies",
+ "Only use what is in the runlevels",
+ "Force an update of the dependency tree",
+ longopts_help_COMMON
+};
+#include "_usage.c"
+
+int rc_depend (int argc, char **argv)
+{
+ char **types = NULL;
+ char **services = NULL;
+ char **depends = NULL;
+ char **list;
+ rc_depinfo_t *deptree = NULL;
+ char *service;
+ int options = RC_DEP_TRACE;
+ bool first = true;
+ int i;
+ bool update = false;
+ char *runlevel = xstrdup( getenv ("RC_SOFTLEVEL"));
+ int opt;
+ char *token;
+
+ applet = basename_c (argv[0]);
+
+ while ((opt = getopt_long (argc, argv, getoptstring,
+ longopts, (int *) 0)) != -1)
+ {
+ switch (opt) {
+ case 's':
+ options |= RC_DEP_STRICT;
+ break;
+ case 't':
+ while ((token = strsep (&optarg, ",")))
+ rc_strlist_addu (&types, token);
+ break;
+ case 'u':
+ update = true;
+ break;
+ case 'T':
+ options &= RC_DEP_TRACE;
+ break;
+
+ case_RC_COMMON_GETOPT
+ }
+ }
+
+ if (update) {
+ bool u = false;
+ ebegin ("Caching service dependencies");
+ u = rc_deptree_update ();
+ eend (u ? 0 : -1, "%s: %s", applet, strerror (errno));
+ if (! u)
+ eerrorx ("Failed to update the dependency tree");
+ }
+
+ if (! (deptree = _rc_deptree_load (NULL)))
+ eerrorx ("failed to load deptree");
+
+ if (! runlevel)
+ runlevel = rc_runlevel_get ();
+
+ while (optind < argc) {
+ list = NULL;
+ rc_strlist_add (&list, argv[optind]);
+ errno = 0;
+ depends = rc_deptree_depends (deptree, NULL, (const char **) list,
+ runlevel, 0);
+ if (! depends && errno == ENOENT)
+ eerror ("no dependency info for service `%s'", argv[optind]);
+ else
+ rc_strlist_add (&services, argv[optind]);
+
+ rc_strlist_free (depends);
+ rc_strlist_free (list);
+ optind++;
+ }
+
+ if (! services) {
+ rc_strlist_free (types);
+ rc_deptree_free (deptree);
+ free (runlevel);
+ if (update)
+ return (EXIT_SUCCESS);
+ eerrorx ("no services specified");
+ }
+
+ /* If we don't have any types, then supply some defaults */
+ if (! types) {
+ rc_strlist_add (&types, "ineed");
+ rc_strlist_add (&types, "iuse");
+ }
+
+ depends = rc_deptree_depends (deptree, (const char **) types,
+ (const char **) services, runlevel, options);
+
+ if (depends) {
+ STRLIST_FOREACH (depends, service, i) {
+ if (first)
+ first = false;
+ else
+ printf (" ");
+
+ if (service)
+ printf ("%s", service);
+
+ }
+ printf ("\n");
+ }
+
+ rc_strlist_free (types);
+ rc_strlist_free (services);
+ rc_strlist_free (depends);
+ rc_deptree_free (deptree);
+ free (runlevel);
+ return (EXIT_SUCCESS);
+}
diff --git a/src/rc/rc-logger.c b/src/rc/rc-logger.c
new file mode 100644
index 0000000..675a4d2
--- /dev/null
+++ b/src/rc/rc-logger.c
@@ -0,0 +1,258 @@
+/*
+ rc-logger.c
+ Spawns a logging daemon to capture stdout and stderr so we can log
+ them to a buffer and/or files.
+ */
+
+/*
+ * Copyright 2007 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.
+ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifdef __linux__
+# include <pty.h>
+#else
+# include <libutil.h>
+#endif
+
+#include "einfo.h"
+#include "rc-logger.h"
+#include "rc-misc.h"
+#include "rc.h"
+
+#define LOGFILE RC_SVCDIR "/rc.log"
+#define PERMLOG "/var/log/rc.log"
+#define MOVELOG "mv " LOGFILE " " PERMLOG ".$$.tmp && cat " PERMLOG \
+ ".$$.tmp >>" PERMLOG " 2>/dev/null && rm -f " PERMLOG ".$$.tmp"
+
+static int signal_pipe[2] = { -1, -1 };
+static int fd_stdout = -1;
+static int fd_stderr = -1;
+static const char *runlevel = NULL;
+static bool in_escape = false;
+static bool in_term = false;
+
+static char *logbuf = NULL;
+static size_t logbuf_size = 0;
+static size_t logbuf_len = 0;
+
+pid_t rc_logger_pid = -1;
+int rc_logger_tty = -1;
+bool rc_in_logger = false;
+
+static void write_log (int logfd, const char *buffer, size_t bytes)
+{
+ const char *p = buffer;
+
+ while ((size_t) (p - buffer) < bytes) {
+ switch (*p) {
+ case '\r':
+ goto cont;
+ case '\033':
+ in_escape = true;
+ in_term = false;
+ goto cont;
+ case '\n':
+ in_escape = in_term = false;
+ break;
+ case '[':
+ if (in_escape)
+ in_term = true;
+ break;
+ }
+
+ if (! in_escape) {
+ write (logfd, p++, 1);
+ continue;
+ }
+
+ if (! in_term || isalpha (*p))
+ in_escape = in_term = false;
+cont:
+ p++;
+ }
+}
+
+static void write_time (FILE *f, const char *s)
+{
+ time_t now = time (NULL);
+ struct tm *tm = localtime (&now);
+
+ fprintf (f, "\nrc %s logging %s at %s\n", runlevel, s, asctime (tm));
+ fflush (f);
+}
+
+void rc_logger_close ()
+{
+ if (signal_pipe[1] > -1) {
+ int sig = SIGTERM;
+ write (signal_pipe[1], &sig, sizeof (sig));
+ close (signal_pipe[1]);
+ signal_pipe[1] = -1;
+ }
+
+ if (rc_logger_pid > 0)
+ waitpid (rc_logger_pid, 0, 0);
+
+ if (fd_stdout > -1)
+ dup2 (fd_stdout, STDOUT_FILENO);
+ if (fd_stderr > -1)
+ dup2 (fd_stderr, STDERR_FILENO);
+}
+
+void rc_logger_open (const char *level)
+{
+ int slave_tty;
+ struct termios tt;
+ struct winsize ws;
+ char *buffer;
+ fd_set rset;
+ int s = 0;
+ size_t bytes;
+ int selfd;
+ int i;
+ FILE *log = NULL;
+
+ if (! isatty (STDOUT_FILENO))
+ return;
+
+ if (! rc_conf_yesno ("rc_logger"))
+ return;
+
+ if (pipe (signal_pipe) == -1)
+ eerrorx ("pipe: %s", strerror (errno));
+ for (i = 0; i < 2; i++)
+ if ((s = fcntl (signal_pipe[i], F_GETFD, 0) == -1 ||
+ fcntl (signal_pipe[i], F_SETFD, s | FD_CLOEXEC) == -1))
+ eerrorx ("fcntl: %s", strerror (errno));
+
+ tcgetattr (STDOUT_FILENO, &tt);
+ ioctl (STDOUT_FILENO, TIOCGWINSZ, &ws);
+
+ /* /dev/pts may not be available yet */
+ if (openpty (&rc_logger_tty, &slave_tty, NULL, &tt, &ws))
+ return;
+
+ rc_logger_pid = fork ();
+ switch (rc_logger_pid) {
+ case -1:
+ eerror ("forkpty: %s", strerror (errno));
+ break;
+ case 0:
+ rc_in_logger = true;
+ close (signal_pipe[1]);
+ signal_pipe[1] = -1;
+
+ runlevel = level;
+ if ((log = fopen (LOGFILE, "a")))
+ write_time (log, "started");
+ else {
+ free (logbuf);
+ logbuf_size = RC_LINEBUFFER * 10;
+ logbuf = xmalloc (sizeof (char) * logbuf_size);
+ logbuf_len = 0;
+ }
+
+ buffer = xmalloc (sizeof (char) * RC_LINEBUFFER);
+ selfd = rc_logger_tty > signal_pipe[0] ? rc_logger_tty : signal_pipe[0];
+ while (1) {
+ FD_ZERO (&rset);
+ FD_SET (rc_logger_tty, &rset);
+ FD_SET (signal_pipe[0], &rset);
+
+ if ((s = select (selfd + 1, &rset, NULL, NULL, NULL)) == -1) {
+ eerror ("select: %s", strerror (errno));
+ break;
+ }
+
+ if (s > 0) {
+ if (FD_ISSET (rc_logger_tty, &rset)) {
+ memset (buffer, 0, RC_LINEBUFFER);
+ bytes = read (rc_logger_tty, buffer, RC_LINEBUFFER);
+ write (STDOUT_FILENO, buffer, bytes);
+
+ if (log)
+ write_log (fileno (log), buffer, bytes);
+ else {
+ if (logbuf_size - logbuf_len < bytes) {
+ logbuf_size += RC_LINEBUFFER * 10;
+ logbuf = xrealloc (logbuf, sizeof (char ) *
+ logbuf_size);
+ }
+
+ memcpy (logbuf + logbuf_len, buffer, bytes);
+ logbuf_len += bytes;
+ }
+ }
+
+ /* Only SIGTERMS signals come down this pipe */
+ if (FD_ISSET (signal_pipe[0], &rset))
+ break;
+ }
+ }
+ free (buffer);
+ if (logbuf) {
+ if ((log = fopen (LOGFILE, "a"))) {
+ write_time (log, "started");
+ write_log (fileno (log), logbuf, logbuf_len);
+ }
+ free (logbuf);
+ }
+ if (log) {
+ write_time (log, "stopped");
+ fclose (log);
+ }
+
+ /* Try and cat our new logfile to a more permament location and then
+ * punt it */
+ system (MOVELOG);
+
+ exit (0);
+ default:
+ setpgid (rc_logger_pid, 0);
+ fd_stdout = dup (STDOUT_FILENO);
+ fd_stderr = dup (STDERR_FILENO);
+ dup2 (slave_tty, STDOUT_FILENO);
+ dup2 (slave_tty, STDERR_FILENO);
+ if (slave_tty != STDIN_FILENO &&
+ slave_tty != STDOUT_FILENO &&
+ slave_tty != STDERR_FILENO)
+ close (slave_tty);
+ close (signal_pipe[0]);
+ signal_pipe[0] = -1;
+ break;
+ }
+}
diff --git a/src/rc/rc-logger.h b/src/rc/rc-logger.h
new file mode 100644
index 0000000..c15e73f
--- /dev/null
+++ b/src/rc/rc-logger.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2007 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.
+ */
+
+pid_t rc_logger_pid;
+int rc_logger_tty;
+extern bool rc_in_logger;
+
+void rc_logger_open (const char *runlevel);
+void rc_logger_close ();
diff --git a/src/rc/rc-misc.c b/src/rc/rc-misc.c
new file mode 100644
index 0000000..0d8b8c1
--- /dev/null
+++ b/src/rc/rc-misc.c
@@ -0,0 +1,353 @@
+/*
+ librc-misc.c
+ rc misc functions
+ */
+
+/*
+ * Copyright 2007 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.
+ */
+
+#include <sys/types.h>
+
+#ifdef __linux__
+#include <sys/sysinfo.h>
+#include <regex.h>
+#endif
+
+#include <sys/utsname.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "rc.h"
+#include "rc-misc.h"
+#include "strlist.h"
+
+#define PROFILE_ENV "/etc/profile.env"
+#define SYS_WHITELIST RC_LIBDIR "/conf.d/env_whitelist"
+#define USR_WHITELIST "/etc/conf.d/env_whitelist"
+#define RC_CONF "/etc/rc.conf"
+#define RC_CONF_OLD "/etc/conf.d/rc"
+
+#define PATH_PREFIX RC_LIBDIR "/bin:/bin:/sbin:/usr/bin:/usr/sbin"
+
+static char **rc_conf = NULL;
+
+static void _free_rc_conf (void)
+{
+ rc_strlist_free (rc_conf);
+}
+
+char *rc_conf_value (const char *setting)
+{
+ if (! rc_conf) {
+ char *line;
+ int i;
+
+ rc_conf = rc_config_load (RC_CONF);
+ atexit (_free_rc_conf);
+
+ /* Support old configs */
+ if (exists (RC_CONF_OLD)) {
+ char **old = rc_config_load (RC_CONF_OLD);
+ rc_strlist_join (&rc_conf, old);
+ rc_strlist_free (old);
+ }
+
+ /* Convert old uppercase to lowercase */
+ STRLIST_FOREACH (rc_conf, line, i) {
+ char *p = line;
+ while (p && *p && *p != '=') {
+ if (isupper (*p))
+ *p = tolower (*p);
+ p++;
+ }
+ }
+ }
+
+ return (rc_config_value (rc_conf, setting));
+}
+
+bool rc_conf_yesno (const char *setting)
+{
+ return (rc_yesno (rc_conf_value (setting)));
+}
+
+char **env_filter (void)
+{
+ char **env = NULL;
+ char **whitelist = NULL;
+ char *env_name = NULL;
+ char **profile = NULL;
+ int count = 0;
+ bool got_path = false;
+ char *env_var;
+ int env_len;
+ char *token;
+ char *sep;
+ char *e;
+ char *p;
+ int pplen = strlen (PATH_PREFIX);
+
+ whitelist = rc_config_list (SYS_WHITELIST);
+ if (! whitelist)
+ fprintf (stderr, "system environment whitelist (" SYS_WHITELIST ") missing\n");
+
+ env = rc_config_list (USR_WHITELIST);
+ rc_strlist_join (&whitelist, env);
+ rc_strlist_free (env);
+ env = NULL;
+
+ if (! whitelist)
+ return (NULL);
+
+ if (exists (PROFILE_ENV))
+ profile = rc_config_load (PROFILE_ENV);
+
+ STRLIST_FOREACH (whitelist, env_name, count) {
+ char *space = strchr (env_name, ' ');
+ if (space)
+ *space = 0;
+
+ env_var = getenv (env_name);
+
+ if (! env_var && profile) {
+ env_len = strlen (env_name) + strlen ("export ") + 1;
+ p = xmalloc (sizeof (char) * env_len);
+ snprintf (p, env_len, "export %s", env_name);
+ env_var = rc_config_value (profile, p);
+ free (p);
+ }
+
+ if (! env_var)
+ continue;
+
+ /* Ensure our PATH is prefixed with the system locations first
+ for a little extra security */
+ if (strcmp (env_name, "PATH") == 0 &&
+ strncmp (PATH_PREFIX, env_var, pplen) != 0)
+ {
+ got_path = true;
+ env_len = strlen (env_name) + strlen (env_var) + pplen + 2;
+ e = p = xmalloc (sizeof (char) * env_len);
+ p += snprintf (e, env_len, "%s=%s", env_name, PATH_PREFIX);
+
+ /* Now go through the env var and only add bits not in our PREFIX */
+ sep = env_var;
+ while ((token = strsep (&sep, ":"))) {
+ char *np = xstrdup (PATH_PREFIX);
+ char *npp = np;
+ char *tok = NULL;
+ while ((tok = strsep (&npp, ":")))
+ if (strcmp (tok, token) == 0)
+ break;
+ if (! tok)
+ p += snprintf (p, env_len - (p - e), ":%s", token);
+ free (np);
+ }
+ *p++ = 0;
+ } else {
+ env_len = strlen (env_name) + strlen (env_var) + 2;
+ e = xmalloc (sizeof (char) * env_len);
+ snprintf (e, env_len, "%s=%s", env_name, env_var);
+ }
+
+ rc_strlist_add (&env, e);
+ free (e);
+ }
+
+ /* We filtered the env but didn't get a PATH? Very odd.
+ However, we do need a path, so use a default. */
+ if (! got_path) {
+ env_len = strlen ("PATH=") + strlen (PATH_PREFIX) + 2;
+ e = xmalloc (sizeof (char) * env_len);
+ snprintf (e, env_len, "PATH=%s", PATH_PREFIX);
+ rc_strlist_add (&env, e);
+ free (e);
+ }
+
+ rc_strlist_free (whitelist);
+ rc_strlist_free (profile);
+
+ return (env);
+}
+
+ /* Other systems may need this at some point, but for now it's Linux only */
+#ifdef __linux__
+static bool file_regex (const char *file, const char *regex)
+{
+ FILE *fp;
+ char *buffer;
+ regex_t re;
+ bool retval = false;
+ int result;
+
+ if (! (fp = fopen (file, "r")))
+ return (false);
+
+ buffer = xmalloc (sizeof (char) * RC_LINEBUFFER);
+ if ((result = regcomp (&re, regex, REG_EXTENDED | REG_NOSUB)) != 0) {
+ fclose (fp);
+ regerror (result, &re, buffer, RC_LINEBUFFER);
+ fprintf (stderr, "file_regex: %s", buffer);
+ free (buffer);
+ return (false);
+ }
+
+ while (fgets (buffer, RC_LINEBUFFER, fp)) {
+ if (regexec (&re, buffer, 0, NULL, 0) == 0)
+ {
+ retval = true;
+ break;
+ }
+ }
+ free (buffer);
+ fclose (fp);
+ regfree (&re);
+
+ return (retval);
+}
+#endif
+
+char **env_config (void)
+{
+ char **env = NULL;
+ char *line;
+ int i;
+#ifdef __linux__
+ char sys[6];
+#endif
+ struct utsname uts;
+ FILE *fp;
+ char buffer[PATH_MAX];
+ char *runlevel = rc_runlevel_get ();
+ char *p;
+
+ /* One char less to drop the trailing / */
+ i = strlen ("RC_LIBDIR=") + strlen (RC_LIBDIR) + 1;
+ line = xmalloc (sizeof (char) * i);
+ snprintf (line, i, "RC_LIBDIR=" RC_LIBDIR);
+ rc_strlist_add (&env, line);
+ free (line);
+
+ /* One char less to drop the trailing / */
+ i = strlen ("RC_SVCDIR=") + strlen (RC_SVCDIR) + 1;
+ line = xmalloc (sizeof (char) * i);
+ snprintf (line, i, "RC_SVCDIR=" RC_SVCDIR);
+ rc_strlist_add (&env, line);
+ free (line);
+
+ rc_strlist_add (&env, "RC_BOOTLEVEL=" RC_LEVEL_BOOT);
+
+ i = strlen ("RC_SOFTLEVEL=") + strlen (runlevel) + 1;
+ line = xmalloc (sizeof (char) * i);
+ snprintf (line, i, "RC_SOFTLEVEL=%s", runlevel);
+ rc_strlist_add (&env, line);
+ free (line);
+
+ if ((fp = fopen (RC_KSOFTLEVEL, "r"))) {
+ memset (buffer, 0, sizeof (buffer));
+ if (fgets (buffer, sizeof (buffer), fp)) {
+ i = strlen (buffer) - 1;
+ if (buffer[i] == '\n')
+ buffer[i] = 0;
+ i += strlen ("RC_DEFAULTLEVEL=") + 2;
+ line = xmalloc (sizeof (char) * i);
+ snprintf (line, i, "RC_DEFAULTLEVEL=%s", buffer);
+ rc_strlist_add (&env, line);
+ free (line);
+ }
+ fclose (fp);
+ } else
+ rc_strlist_add (&env, "RC_DEFAULTLEVEL=" RC_LEVEL_DEFAULT);
+
+
+#ifdef __linux__
+ /* Linux can run some funky stuff like Xen, VServer, UML, etc
+ We store this special system in RC_SYS so our scripts run fast */
+ memset (sys, 0, sizeof (sys));
+
+ if (exists ("/proc/xen")) {
+ if ((fp = fopen ("/proc/xen/capabilities", "r"))) {
+ fclose (fp);
+ if (file_regex ("/proc/xen/capabilities", "control_d"))
+ snprintf (sys, sizeof (sys), "XEN0");
+ }
+ if (! sys[0])
+ snprintf (sys, sizeof (sys), "XENU");
+ } else if (file_regex ("/proc/cpuinfo", "UML")) {
+ snprintf (sys, sizeof (sys), "UML");
+ } else if (file_regex ("/proc/self/status",
+ "(s_context|VxID|envID):[[:space:]]*[1-9]"))
+ {
+ snprintf (sys, sizeof (sys), "VPS");
+ }
+
+ if (sys[0]) {
+ i = strlen ("RC_SYS=") + strlen (sys) + 2;
+ line = xmalloc (sizeof (char) * i);
+ snprintf (line, i, "RC_SYS=%s", sys);
+ rc_strlist_add (&env, line);
+ free (line);
+ }
+
+#endif
+
+ /* Some scripts may need to take a different code path if Linux/FreeBSD, etc
+ To save on calling uname, we store it in an environment variable */
+ if (uname (&uts) == 0) {
+ i = strlen ("RC_UNAME=") + strlen (uts.sysname) + 2;
+ line = xmalloc (sizeof (char) * i);
+ snprintf (line, i, "RC_UNAME=%s", uts.sysname);
+ rc_strlist_add (&env, line);
+ free (line);
+ }
+
+ /* Be quiet or verbose as necessary */
+ if ((p = rc_conf_value ("rc_quiet"))) {
+ i = strlen ("EINFO_QUIET=") + strlen (p) + 1;
+ line = xmalloc (sizeof (char) * i);
+ snprintf (line, i, "EINFO_QUIET=%s", p);
+ rc_strlist_add (&env, line);
+ free (line);
+ }
+ if ((p = rc_conf_value ("rc_verbose"))) {
+ i = strlen ("EINFO_VERBOSE=") + strlen (p) + 1;
+ line = xmalloc (sizeof (char) * i);
+ snprintf (line, i, "EINFO_VERBOSE=%s", p);
+ rc_strlist_add (&env, line);
+ free (line);
+ }
+
+ errno = 0;
+ if ((! rc_conf_yesno ("rc_color") && errno == 0) ||
+ rc_conf_yesno ("rc_nocolor"))
+ rc_strlist_add (&env, "EINFO_COLOR=no");
+
+ free (runlevel);
+ return (env);
+}
diff --git a/src/rc/rc-plugin.c b/src/rc/rc-plugin.c
new file mode 100644
index 0000000..613f049
--- /dev/null
+++ b/src/rc/rc-plugin.c
@@ -0,0 +1,241 @@
+/*
+ librc-plugin.c
+ Simple plugin handler
+ */
+
+/*
+ * Copyright 2007 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.
+ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "rc-plugin.h"
+#include "strlist.h"
+
+#define RC_PLUGIN_HOOK "rc_plugin_hook"
+
+bool rc_in_plugin = false;
+
+typedef struct plugin
+{
+ char *name;
+ void *handle;
+ int (*hook) (rc_hook_t, const char *);
+ struct plugin *next;
+} plugin_t;
+
+static plugin_t *plugins = NULL;
+
+#ifndef __FreeBSD__
+dlfunc_t dlfunc (void * __restrict handle, const char * __restrict symbol)
+{
+ union {
+ void *d;
+ dlfunc_t f;
+ } rv;
+
+ rv.d = dlsym (handle, symbol);
+ return (rv.f);
+}
+#endif
+
+void rc_plugin_load (void)
+{
+ DIR *dp;
+ struct dirent *d;
+ plugin_t *plugin = plugins;
+ char *p;
+ void *h;
+ int (*fptr) (rc_hook_t, const char *);
+
+ /* Don't load plugins if we're in one */
+ if (rc_in_plugin)
+ return;
+
+ /* Ensure some sanity here */
+ rc_plugin_unload ();
+
+ if (! (dp = opendir (RC_PLUGINDIR)))
+ return;
+
+ while ((d = readdir (dp))) {
+ if (d->d_name[0] == '.')
+ continue;
+
+ p = rc_strcatpaths (RC_PLUGINDIR, d->d_name, NULL);
+ h = dlopen (p, RTLD_LAZY);
+ free (p);
+ if (! h) {
+ eerror ("dlopen: %s", dlerror ());
+ continue;
+ }
+
+ fptr = (int (*)(rc_hook_t, const char*)) dlfunc (h, RC_PLUGIN_HOOK);
+ if (! fptr) {
+ eerror ("%s: cannot find symbol `%s'", d->d_name, RC_PLUGIN_HOOK);
+ dlclose (h);
+ } else {
+ if (plugin) {
+ plugin->next = xmalloc (sizeof (plugin_t));
+ plugin = plugin->next;
+ } else
+ plugin = plugins = xmalloc (sizeof (plugin_t));
+
+ memset (plugin, 0, sizeof (plugin_t));
+ plugin->name = xstrdup (d->d_name);
+ plugin->handle = h;
+ plugin->hook = fptr;
+ }
+ }
+ closedir (dp);
+}
+
+int rc_waitpid (pid_t pid)
+{
+ int status = 0;
+ pid_t savedpid = pid;
+ int retval = -1;
+
+ errno = 0;
+ while ((pid = waitpid (savedpid, &status, 0)) > 0) {
+ if (pid == savedpid)
+ retval = WIFEXITED (status) ? WEXITSTATUS (status) : EXIT_FAILURE;
+ }
+
+ return (retval);
+}
+
+void rc_plugin_run (rc_hook_t hook, const char *value)
+{
+ plugin_t *plugin = plugins;
+
+ /* Don't run plugins if we're in one */
+ if (rc_in_plugin)
+ return;
+
+ while (plugin) {
+ if (plugin->hook) {
+ int i;
+ int flags;
+ int pfd[2];
+ pid_t pid;
+
+ /* We create a pipe so that plugins can affect our environment
+ * vars, which in turn influence our scripts. */
+ if (pipe (pfd) == -1) {
+ eerror ("pipe: %s", strerror (errno));
+ return;
+ }
+
+ /* Stop any scripts from inheriting us.
+ * This is actually quite important as without this, the splash
+ * plugin will probably hang when running in silent mode. */
+ for (i = 0; i < 2; i++)
+ if ((flags = fcntl (pfd[i], F_GETFD, 0)) < 0 ||
+ fcntl (pfd[i], F_SETFD, flags | FD_CLOEXEC) < 0)
+ eerror ("fcntl: %s", strerror (errno));
+
+ /* We run the plugin in a new process so we never crash
+ * or otherwise affected by it */
+ if ((pid = fork ()) == -1) {
+ eerror ("fork: %s", strerror (errno));
+ return;
+ }
+
+ if (pid == 0) {
+ int retval;
+
+ rc_in_plugin = true;
+ close (pfd[0]);
+ rc_environ_fd = fdopen (pfd[1], "w");
+ retval = plugin->hook (hook, value);
+ fclose (rc_environ_fd);
+ rc_environ_fd = NULL;
+
+ /* Just in case the plugin sets this to false */
+ rc_in_plugin = true;
+ exit (retval);
+ } else {
+ char *buffer;
+ char *token;
+ char *p;
+ ssize_t nr;
+
+ close (pfd[1]);
+ buffer = xmalloc (sizeof (char) * RC_LINEBUFFER);
+ memset (buffer, 0, RC_LINEBUFFER);
+
+ while ((nr = read (pfd[0], buffer, RC_LINEBUFFER)) > 0) {
+ p = buffer;
+ while (*p && p - buffer < nr) {
+ token = strsep (&p, "=");
+ if (token) {
+ unsetenv (token);
+ if (*p) {
+ setenv (token, p, 1);
+ p += strlen (p) + 1;
+ } else
+ p++;
+ }
+ }
+ }
+
+ free (buffer);
+ close (pfd[0]);
+
+ rc_waitpid (pid);
+ }
+ }
+ plugin = plugin->next;
+ }
+}
+
+void rc_plugin_unload (void)
+{
+ plugin_t *plugin = plugins;
+ plugin_t *next;
+
+ while (plugin) {
+ next = plugin->next;
+ dlclose (plugin->handle);
+ free (plugin->name);
+ free (plugin);
+ plugin = next;
+ }
+ plugins = NULL;
+}
diff --git a/src/rc/rc-plugin.h b/src/rc/rc-plugin.h
new file mode 100644
index 0000000..412a47e
--- /dev/null
+++ b/src/rc/rc-plugin.h
@@ -0,0 +1,55 @@
+/*
+ librc-plugin.h
+ Private instructions to use plugins
+ */
+
+/*
+ * Copyright 2007 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.
+ */
+
+#ifndef __LIBRC_PLUGIN_H__
+#define __LIBRC_PLUGIN_H__
+
+/* A simple flag to say if we're in a plugin proccess or not.
+ * Mainly used in atexit code. */
+extern bool rc_in_plugin;
+
+int rc_waitpid (pid_t pid);
+void rc_plugin_load ();
+void rc_plugin_unload ();
+void rc_plugin_run (rc_hook_t, const char *value);
+
+/* dlfunc defines needed to avoid ISO errors. FreeBSD has this right :) */
+#ifndef __FreeBSD__
+struct __dlfunc_arg {
+ int __dlfunc_dummy;
+};
+
+typedef void (*dlfunc_t) (struct __dlfunc_arg);
+
+dlfunc_t dlfunc (void * __restrict handle, const char * __restrict symbol);
+#endif
+
+#endif
diff --git a/src/rc/rc-status.c b/src/rc/rc-status.c
new file mode 100644
index 0000000..155192f
--- /dev/null
+++ b/src/rc/rc-status.c
@@ -0,0 +1,197 @@
+/*
+ rc-status
+ Display the status of the services in runlevels
+ */
+
+/*
+ * Copyright 2007 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.
+ */
+
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "builtins.h"
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "strlist.h"
+
+static const char *applet;
+
+static const char *types_nua[] = { "ineed", "iuse", "iafter", NULL };
+
+static void print_level (char *level)
+{
+ printf ("Runlevel: %s%s%s\n",
+ ecolor (ECOLOR_HILITE),
+ level,
+ ecolor (ECOLOR_NORMAL));
+}
+
+static void print_service (char *service)
+{
+ char status[10];
+ int cols = printf (" %s", service);
+ const char *c = ecolor (ECOLOR_GOOD);
+ rc_service_state_t state = rc_service_state (service);
+ einfo_color_t color = ECOLOR_BAD;
+
+ if (state & RC_SERVICE_STOPPING)
+ snprintf (status, sizeof (status), "stopping ");
+ else if (state & RC_SERVICE_STARTING) {
+ snprintf (status, sizeof (status), "starting ");
+ color = ECOLOR_WARN;
+ } else if (state & RC_SERVICE_INACTIVE) {
+ snprintf (status, sizeof (status), "inactive ");
+ color = ECOLOR_WARN;
+ } else if (state & RC_SERVICE_STARTED) {
+ if (geteuid () == 0 && rc_service_daemons_crashed (service))
+ snprintf (status, sizeof (status), " crashed ");
+ else {
+ snprintf (status, sizeof (status), " started ");
+ color = ECOLOR_GOOD;
+ }
+ } else if (state & RC_SERVICE_SCHEDULED) {
+ snprintf (status, sizeof (status), "scheduled");
+ color = ECOLOR_WARN;
+ } else
+ snprintf (status, sizeof (status), " stopped ");
+
+ errno = 0;
+ if (c && *c && isatty (fileno (stdout)))
+ printf ("\n");
+ ebracket (cols, color, status);
+}
+
+#include "_usage.h"
+#define extraopts "[runlevel1] [runlevel2] ..."
+#define getoptstring "alsu" getoptstring_COMMON
+static const struct option longopts[] = {
+ {"all", 0, NULL, 'a'},
+ {"list", 0, NULL, 'l'},
+ {"servicelist", 0, NULL, 's'},
+ {"unused", 0, NULL, 'u'},
+ longopts_COMMON
+};
+static const char * const longopts_help[] = {
+ "Show services from all run levels",
+ "Show list of run levels",
+ "Show service list",
+ "Show services not assigned to any runlevel",
+ longopts_help_COMMON
+};
+#include "_usage.c"
+
+int rc_status (int argc, char **argv)
+{
+ rc_depinfo_t *deptree = NULL;
+ char **levels = NULL;
+ char **services = NULL;
+ char **ordered = NULL;
+ char *level;
+ char *service;
+ int opt;
+ int i;
+ int j;
+ int depopts = RC_DEP_STRICT | RC_DEP_START | RC_DEP_TRACE;
+
+ while ((opt = getopt_long (argc, argv, getoptstring, longopts,
+ (int *) 0)) != -1)
+ switch (opt) {
+ case 'a':
+ levels = rc_runlevel_list ();
+ break;
+ case 'l':
+ levels = rc_runlevel_list ();
+ STRLIST_FOREACH (levels, level, i)
+ printf ("%s\n", level);
+ rc_strlist_free (levels);
+ exit (EXIT_SUCCESS);
+ case 's':
+ services = rc_services_in_runlevel (NULL);
+ STRLIST_FOREACH (services, service, i)
+ print_service (service);
+ rc_strlist_free (services);
+ exit (EXIT_SUCCESS);
+ case 'u':
+ services = rc_services_in_runlevel (NULL);
+ levels = rc_runlevel_list ();
+ STRLIST_FOREACH (services, service, i) {
+ bool found = false;
+ STRLIST_FOREACH (levels, level, j)
+ if (rc_service_in_runlevel (service, level)) {
+ found = true;
+ break;
+ }
+ if (! found)
+ print_service (service);
+ }
+ rc_strlist_free (levels);
+ rc_strlist_free (services);
+ exit (EXIT_SUCCESS);
+
+ case_RC_COMMON_GETOPT
+ }
+
+ while (optind < argc)
+ rc_strlist_add (&levels, argv[optind++]);
+
+ if (! levels) {
+ level = rc_runlevel_get ();
+ rc_strlist_add (&levels, level);
+ free (level);
+ }
+
+ /* Output the services in the order in which they would start */
+ if (geteuid () == 0)
+ deptree = _rc_deptree_load (NULL);
+ else
+ deptree = rc_deptree_load ();
+
+ STRLIST_FOREACH (levels, level, i) {
+ print_level (level);
+ services = rc_services_in_runlevel (level);
+ if (deptree) {
+ ordered = rc_deptree_depends (deptree, types_nua,
+ (const char **) services,
+ level, depopts);
+ rc_strlist_free (services);
+ services = ordered;
+ ordered = NULL;
+ }
+ STRLIST_FOREACH (services, service, j)
+ if (rc_service_in_runlevel (service, level))
+ print_service (service);
+ rc_strlist_free (services);
+ }
+
+ rc_strlist_free (levels);
+ rc_deptree_free (deptree);
+
+ return (EXIT_SUCCESS);
+}
diff --git a/src/rc/rc-update.c b/src/rc/rc-update.c
new file mode 100644
index 0000000..4f07503
--- /dev/null
+++ b/src/rc/rc-update.c
@@ -0,0 +1,272 @@
+/*
+ rc-update
+ Manage init scripts and runlevels
+ */
+
+/*
+ * Copyright 2007 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.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "builtins.h"
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "strlist.h"
+
+static const char *applet = NULL;
+
+/* Return the number of changes made:
+ * -1 = no changes (error)
+ * 0 = no changes (nothing to do)
+ * 1+ = number of runlevels updated
+ */
+static int add (const char *runlevel, const char *service)
+{
+ int retval = -1;
+
+ if (! rc_service_exists (service))
+ eerror ("%s: service `%s' does not exist", applet, service);
+ else if (rc_service_in_runlevel (service, runlevel)) {
+ ewarn ("%s: %s already installed in runlevel `%s'; skipping",
+ applet, service, runlevel);
+ retval = 0;
+ } else if (rc_service_add (runlevel, service)) {
+ einfo ("%s added to runlevel %s", service, runlevel);
+ retval = 1;
+ } else
+ eerror ("%s: failed to add service `%s' to runlevel `%s': %s",
+ applet, service, runlevel, strerror (errno));
+
+ return (retval);
+}
+
+static int delete (const char *runlevel, const char *service)
+{
+ int retval = -1;
+
+ errno = 0;
+ if (rc_service_delete (runlevel, service)) {
+ einfo ("%s removed from runlevel %s", service, runlevel);
+ return 1;
+ }
+
+ if (errno == ENOENT)
+ eerror ("%s: service `%s' is not in the runlevel `%s'",
+ applet, service, runlevel);
+ else
+ eerror ("%s: failed to remove service `%s' from runlevel `%s': %s",
+ applet, service, runlevel, strerror (errno));
+
+ return (retval);
+}
+
+static void show (char **runlevels, bool verbose)
+{
+ char *service;
+ char **services = rc_services_in_runlevel (NULL);
+ char *runlevel;
+ int i;
+ int j;
+
+ STRLIST_FOREACH (services, service, i) {
+ char **in = NULL;
+ bool inone = false;
+
+ STRLIST_FOREACH (runlevels, runlevel, j) {
+ if (rc_service_in_runlevel (service, runlevel)) {
+ rc_strlist_add (&in, runlevel);
+ inone = true;
+ } else {
+ char buffer[PATH_MAX];
+ memset (buffer, ' ', strlen (runlevel));
+ buffer[strlen (runlevel)] = 0;
+ rc_strlist_add (&in, buffer);
+ }
+ }
+
+ if (! inone && ! verbose)
+ continue;
+
+ printf (" %20s |", service);
+ STRLIST_FOREACH (in, runlevel, j)
+ printf (" %s", runlevel);
+ printf ("\n");
+ rc_strlist_free (in);
+ }
+
+ rc_strlist_free (services);
+}
+
+#include "_usage.h"
+#define getoptstring "ads" getoptstring_COMMON
+static struct option longopts[] = {
+ { "add", 0, NULL, 'a'},
+ { "delete", 0, NULL, 'd'},
+ { "show", 0, NULL, 's'},
+ longopts_COMMON
+};
+static const char * const longopts_help[] = {
+ "Add the init.d to runlevels",
+ "Delete init.d from runlevels",
+ "Show init.d's in runlevels",
+ longopts_help_COMMON
+};
+#include "_usage.c"
+
+#define DOADD (1 << 1)
+#define DODELETE (1 << 2)
+#define DOSHOW (1 << 3)
+
+int rc_update (int argc, char **argv)
+{
+ int i;
+ char *service = NULL;
+ char **runlevels = NULL;
+ char *runlevel;
+ int action = 0;
+ bool verbose = false;
+ int opt;
+ int retval = EXIT_FAILURE;
+
+ applet = basename_c (argv[0]);
+
+ while ((opt = getopt_long (argc, argv, getoptstring,
+ longopts, (int *) 0)) != -1)
+ {
+ switch (opt) {
+ case 'a':
+ action |= DOADD;
+ break;
+ case 'd':
+ action |= DODELETE;
+ break;
+ case 's':
+ action |= DOSHOW;
+ break;
+
+ case_RC_COMMON_GETOPT
+ }
+ }
+
+ verbose = rc_yesno (getenv ("EINFO_VERBOSE"));
+
+ if ((action & DOSHOW && action != DOSHOW) ||
+ (action & DOADD && action != DOADD) ||
+ (action & DODELETE && action != DODELETE))
+ eerrorx ("%s: cannot mix commands", applet);
+
+ /* We need to be backwards compatible */
+ if (! action) {
+ if (optind < argc) {
+ if (strcmp (argv[optind], "add") == 0)
+ action = DOADD;
+ else if (strcmp (argv[optind], "delete") == 0 ||
+ strcmp (argv[optind], "del") == 0)
+ action = DODELETE;
+ else if (strcmp (argv[optind], "show") == 0)
+ action = DOSHOW;
+ if (action)
+ optind++;
+ else
+ eerrorx ("%s: invalid command `%s'", applet, argv[optind]);
+ }
+ if (! action)
+ usage (EXIT_FAILURE);
+ }
+
+ if (optind >= argc) {
+ if (! action & DOSHOW)
+ eerrorx ("%s: no service specified", applet);
+ } else {
+ service = argv[optind];
+ optind++;
+
+ while (optind < argc)
+ if (rc_runlevel_exists (argv[optind]))
+ rc_strlist_add (&runlevels, argv[optind++]);
+ else {
+ rc_strlist_free (runlevels);
+ eerrorx ("%s: `%s' is not a valid runlevel", applet, argv[optind]);
+ }
+ }
+
+ retval = EXIT_SUCCESS;
+ if (action & DOSHOW) {
+ if (service)
+ rc_strlist_add (&runlevels, service);
+ if (! runlevels)
+ runlevels = rc_runlevel_list ();
+
+ show (runlevels, verbose);
+ } else {
+ if (! service)
+ eerror ("%s: no service specified", applet);
+ else {
+ int num_updated = 0;
+ int (*actfunc)(const char *, const char *);
+ int ret;
+
+ if (action & DOADD) {
+ actfunc = add;
+ } else if (action & DODELETE) {
+ actfunc = delete;
+ } else
+ eerrorx ("%s: invalid action", applet);
+
+ if (! runlevels)
+ rc_strlist_add (&runlevels, rc_runlevel_get ());
+
+ if (! runlevels)
+ eerrorx ("%s: no runlevels found", applet);
+
+ STRLIST_FOREACH (runlevels, runlevel, i) {
+ if (! rc_runlevel_exists (runlevel)) {
+ eerror ("%s: runlevel `%s' does not exist", applet, runlevel);
+ continue;
+ }
+
+ ret = actfunc (runlevel, service);
+ if (ret < 0)
+ retval = EXIT_FAILURE;
+ num_updated += ret;
+ }
+
+ if (retval == EXIT_SUCCESS && num_updated == 0 && action & DODELETE)
+ ewarnx ("%s: service `%s' not found in any of the specified runlevels", applet, service);
+ }
+ }
+
+ rc_strlist_free (runlevels);
+ return (retval);
+}
diff --git a/src/rc/rc.c b/src/rc/rc.c
new file mode 100644
index 0000000..a33b6dc
--- /dev/null
+++ b/src/rc/rc.c
@@ -0,0 +1,1574 @@
+/*
+ rc.c
+ rc - manager for init scripts which control the startup, shutdown
+ and the running of daemons.
+
+ Also a multicall binary for various commands that can be used in shell
+ scripts to query service state, mark service state and provide the
+ einfo family of informational 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 rc_copyright[] = "Copyright (c) 2007-2008 Roy Marples";
+
+#define SYSLOG_NAMES
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <getopt.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <strings.h>
+#include <syslog.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "builtins.h"
+#include "einfo.h"
+#include "rc.h"
+#include "rc-logger.h"
+#include "rc-misc.h"
+#include "rc-plugin.h"
+#include "strlist.h"
+
+#include "version.h"
+
+#define INITSH RC_LIBDIR "/sh/init.sh"
+#define INITEARLYSH RC_LIBDIR "/sh/init-early.sh"
+#define HALTSH RC_INITDIR "/halt.sh"
+
+#define SHUTDOWN "/sbin/shutdown"
+#define SULOGIN "/sbin/sulogin"
+
+#define INTERACTIVE RC_SVCDIR "/interactive"
+
+#define DEVBOOT "/dev/.rcboot"
+
+/* Cleanup anything in main */
+#define CHAR_FREE(_item) if (_item) { \
+ free (_item); \
+ _item = NULL; \
+}
+
+extern char **environ;
+
+static char *RUNLEVEL = NULL;
+static char *PREVLEVEL = NULL;
+
+static const char *applet = NULL;
+static char *runlevel = NULL;
+static char **env = NULL;
+static char **newenv = NULL;
+static char **coldplugged_services = NULL;
+static char **stop_services = NULL;
+static char **start_services = NULL;
+static rc_depinfo_t *deptree = NULL;
+static char *tmp = NULL;
+
+struct termios *termios_orig = NULL;
+
+typedef struct pidlist
+{
+ pid_t pid;
+ struct pidlist *next;
+} pidlist_t;
+static pidlist_t *service_pids = NULL;
+
+static const char *const types_n[] = { "needsme", NULL };
+static const char *const types_nua[] = { "ineed", "iuse", "iafter", NULL };
+
+static void clean_failed (void)
+{
+ DIR *dp;
+ struct dirent *d;
+ int i;
+ char *path;
+
+ /* Clean the failed services state dir now */
+ if ((dp = opendir (RC_SVCDIR "/failed"))) {
+ while ((d = readdir (dp))) {
+ if (d->d_name[0] == '.' &&
+ (d->d_name[1] == '\0' ||
+ (d->d_name[1] == '.' && d->d_name[2] == '\0')))
+ continue;
+
+ i = strlen (RC_SVCDIR "/failed/") + strlen (d->d_name) + 1;
+ path = xmalloc (sizeof (char) * i);
+ snprintf (path, i, RC_SVCDIR "/failed/%s", d->d_name);
+ if (path) {
+ if (unlink (path))
+ eerror ("%s: unlink `%s': %s", applet, path,
+ strerror (errno));
+ free (path);
+ }
+ }
+ closedir (dp);
+ }
+}
+
+static void cleanup (void)
+{
+ if (applet && strcmp (applet, "rc") == 0) {
+ pidlist_t *pl = service_pids;
+
+ rc_plugin_unload ();
+
+ if (! rc_in_plugin && termios_orig) {
+ tcsetattr (fileno (stdin), TCSANOW, termios_orig);
+ free (termios_orig);
+ }
+
+ while (pl) {
+ pidlist_t *p = pl->next;
+ free (pl);
+ pl = p;
+ }
+
+ rc_strlist_free (env);
+ rc_strlist_free (newenv);
+ rc_strlist_free (coldplugged_services);
+ rc_strlist_free (stop_services);
+ rc_strlist_free (start_services);
+ rc_deptree_free (deptree);
+
+ /* Clean runlevel start, stop markers */
+ if (! rc_in_plugin && ! rc_in_logger) {
+ rmdir (RC_STARTING);
+ rmdir (RC_STOPPING);
+ clean_failed ();
+
+ rc_logger_close ();
+ }
+
+ free (runlevel);
+ }
+}
+
+static int syslog_decode (char *name, CODE *codetab)
+{
+ CODE *c;
+
+ if (isdigit (*name))
+ return (atoi (name));
+
+ for (c = codetab; c->c_name; c++)
+ if (! strcasecmp (name, c->c_name))
+ return (c->c_val);
+
+ return (-1);
+}
+
+static int do_e (int argc, char **argv)
+{
+ int retval = EXIT_SUCCESS;
+ int i;
+ int l = 0;
+ char *message = NULL;
+ char *p;
+ char *fmt = NULL;
+ int level = 0;
+
+ if (strcmp (applet, "eval_ecolors") == 0) {
+ printf ("GOOD='%s'\nWARN='%s'\nBAD='%s'\nHILITE='%s'\nBRACKET='%s'\nNORMAL='%s'\n",
+ ecolor (ECOLOR_GOOD),
+ ecolor (ECOLOR_WARN),
+ ecolor (ECOLOR_BAD),
+ ecolor (ECOLOR_HILITE),
+ ecolor (ECOLOR_BRACKET),
+ ecolor (ECOLOR_NORMAL));
+ exit (EXIT_SUCCESS);
+ }
+
+ if (argc > 0) {
+
+ if (strcmp (applet, "eend") == 0 ||
+ strcmp (applet, "ewend") == 0 ||
+ strcmp (applet, "veend") == 0 ||
+ strcmp (applet, "vweend") == 0)
+ {
+ errno = 0;
+ retval = strtol (argv[0], NULL, 0);
+ if (errno != 0)
+ retval = EXIT_FAILURE;
+ else {
+ argc--;
+ argv++;
+ }
+ } else if (strcmp (applet, "esyslog") == 0 ||
+ strcmp (applet, "elog") == 0) {
+ char *dot = strchr (argv[0], '.');
+ if ((level = syslog_decode (dot + 1, prioritynames)) == -1)
+ eerrorx ("%s: invalid log level `%s'", applet, argv[0]);
+
+ if (argc < 3)
+ eerrorx ("%s: not enough arguments", applet);
+
+ unsetenv ("EINFO_LOG");
+ setenv ("EINFO_LOG", argv[1], 1);
+
+ argc -= 2;
+ argv += 2;
+ }
+ }
+
+ if (argc > 0) {
+ for (i = 0; i < argc; i++)
+ l += strlen (argv[i]) + 1;
+
+ message = xmalloc (l);
+ p = message;
+
+ for (i = 0; i < argc; i++) {
+ if (i > 0)
+ *p++ = ' ';
+ memcpy (p, argv[i], strlen (argv[i]));
+ p += strlen (argv[i]);
+ }
+ *p = 0;
+ }
+
+ if (message)
+ fmt = xstrdup ("%s");
+
+ if (strcmp (applet, "einfo") == 0)
+ einfo (fmt, message);
+ else if (strcmp (applet, "einfon") == 0)
+ einfon (fmt, message);
+ else if (strcmp (applet, "ewarn") == 0)
+ ewarn (fmt, message);
+ else if (strcmp (applet, "ewarnn") == 0)
+ ewarnn (fmt, message);
+ else if (strcmp (applet, "eerror") == 0) {
+ eerror (fmt, message);
+ retval = 1;
+ } else if (strcmp (applet, "eerrorn") == 0) {
+ eerrorn (fmt, message);
+ retval = 1;
+ } else if (strcmp (applet, "ebegin") == 0)
+ ebegin (fmt, message);
+ else if (strcmp (applet, "eend") == 0)
+ eend (retval, fmt, message);
+ else if (strcmp (applet, "ewend") == 0)
+ ewend (retval, fmt, message);
+ else if (strcmp (applet, "esyslog") == 0)
+ elog (level, fmt, message);
+ else if (strcmp (applet, "veinfo") == 0)
+ einfov (fmt, message);
+ else if (strcmp (applet, "veinfon") == 0)
+ einfovn (fmt, message);
+ else if (strcmp (applet, "vewarn") == 0)
+ ewarnv (fmt, message);
+ else if (strcmp (applet, "vewarnn") == 0)
+ ewarnvn (fmt, message);
+ else if (strcmp (applet, "vebegin") == 0)
+ ebeginv (fmt, message);
+ else if (strcmp (applet, "veend") == 0)
+ eendv (retval, fmt, message);
+ else if (strcmp (applet, "vewend") == 0)
+ ewendv (retval, fmt, message);
+ else if (strcmp (applet, "eindent") == 0)
+ eindent ();
+ else if (strcmp (applet, "eoutdent") == 0)
+ eoutdent ();
+ else if (strcmp (applet, "veindent") == 0)
+ eindentv ();
+ else if (strcmp (applet, "veoutdent") == 0)
+ eoutdentv ();
+ else {
+ eerror ("%s: unknown applet", applet);
+ retval = EXIT_FAILURE;
+ }
+
+ if (fmt)
+ free (fmt);
+ if (message)
+ free (message);
+ return (retval);
+}
+
+static int do_service (int argc, char **argv)
+{
+ bool ok = false;
+ char *service = NULL;
+
+ if (argc > 0)
+ service = argv[0];
+ else
+ service = getenv ("SVCNAME");
+
+ if (! service || strlen (service) == 0)
+ eerrorx ("%s: no service specified", applet);
+
+ if (strcmp (applet, "service_started") == 0)
+ ok = (rc_service_state (service) & RC_SERVICE_STARTED);
+ else if (strcmp (applet, "service_stopped") == 0)
+ ok = (rc_service_state (service) & RC_SERVICE_STOPPED);
+ else if (strcmp (applet, "service_inactive") == 0)
+ ok = (rc_service_state (service) & RC_SERVICE_INACTIVE);
+ else if (strcmp (applet, "service_starting") == 0)
+ ok = (rc_service_state (service) & RC_SERVICE_STARTING);
+ else if (strcmp (applet, "service_stopping") == 0)
+ ok = (rc_service_state (service) & RC_SERVICE_STOPPING);
+ else if (strcmp (applet, "service_coldplugged") == 0)
+ ok = (rc_service_state (service) & RC_SERVICE_COLDPLUGGED);
+ else if (strcmp (applet, "service_wasinactive") == 0)
+ ok = (rc_service_state (service) & RC_SERVICE_WASINACTIVE);
+ else if (strcmp (applet, "service_started_daemon") == 0) {
+ int idx = 0;
+ char *d = argv[0];
+
+ service = getenv ("SVCNAME");
+ if (argc > 2) {
+ service = argv[0];
+ d = argv[1];
+ sscanf (argv[2], "%d", &idx);
+ } else if (argc == 2) {
+ sscanf (argv[1], "%d", &idx);
+ }
+ exit (rc_service_started_daemon (service, d, idx) ? 0 : 1);
+ } else
+ eerrorx ("%s: unknown applet", applet);
+
+ return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+static int do_mark_service (int argc, char **argv)
+{
+ bool ok = false;
+ char *svcname = getenv ("SVCNAME");
+ char *service = NULL;
+
+ if (argc > 0)
+ service = argv[0];
+ else
+ service = getenv ("SVCNAME");
+
+ if (! service || strlen (service) == 0)
+ eerrorx ("%s: no service specified", applet);
+
+ if (strcmp (applet, "mark_service_started") == 0)
+ ok = rc_service_mark (service, RC_SERVICE_STARTED);
+ else if (strcmp (applet, "mark_service_stopped") == 0)
+ ok = rc_service_mark (service, RC_SERVICE_STOPPED);
+ else if (strcmp (applet, "mark_service_inactive") == 0)
+ ok = rc_service_mark (service, RC_SERVICE_INACTIVE);
+ else if (strcmp (applet, "mark_service_starting") == 0)
+ ok = rc_service_mark (service, RC_SERVICE_STARTING);
+ else if (strcmp (applet, "mark_service_stopping") == 0)
+ ok = rc_service_mark (service, RC_SERVICE_STOPPING);
+ else if (strcmp (applet, "mark_service_coldplugged") == 0)
+ ok = rc_service_mark (service, RC_SERVICE_COLDPLUGGED);
+ else if (strcmp (applet, "mark_service_failed") == 0)
+ ok = rc_service_mark (service, RC_SERVICE_FAILED);
+ else
+ eerrorx ("%s: unknown applet", applet);
+
+ /* If we're marking ourselves then we need to inform our parent runscript
+ process so they do not mark us based on our exit code */
+ if (ok && svcname && strcmp (svcname, service) == 0) {
+ char *runscript_pid = getenv ("RC_RUNSCRIPT_PID");
+ char *mtime;
+ pid_t pid = 0;
+ int l;
+
+ if (runscript_pid && sscanf (runscript_pid, "%d", &pid) == 1)
+ if (kill (pid, SIGHUP) != 0)
+ eerror ("%s: failed to signal parent %d: %s",
+ applet, pid, strerror (errno));
+
+ /* Remove the exclusive time test. This ensures that it's not
+ in control as well */
+ l = strlen (RC_SVCDIR "exclusive") +
+ strlen (svcname) +
+ strlen (runscript_pid) +
+ 4;
+ mtime = xmalloc (l);
+ snprintf (mtime, l, RC_SVCDIR "exclusive/%s.%s",
+ svcname, runscript_pid);
+ if (exists (mtime) && unlink (mtime) != 0)
+ eerror ("%s: unlink: %s", applet, strerror (errno));
+ free (mtime);
+ }
+
+ return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+static int do_value (int argc, char **argv)
+{
+ bool ok = false;
+ char *service = getenv ("SVCNAME");
+
+ if (! service)
+ eerrorx ("%s: no service specified", applet);
+
+ if (argc < 1 || ! argv[0] || strlen (argv[0]) == 0)
+ eerrorx ("%s: no option specified", applet);
+
+ if (strcmp (applet, "service_get_value") == 0 ||
+ strcmp (applet, "get_options") == 0)
+ {
+ char *option = rc_service_value_get (service, argv[0]);
+ if (option) {
+ printf ("%s", option);
+ free (option);
+ ok = true;
+ }
+ } else if (strcmp (applet, "service_set_value") == 0 ||
+ strcmp (applet, "save_options") == 0)
+ ok = rc_service_value_set (service, argv[0], argv[1]);
+ else
+ eerrorx ("%s: unknown applet", applet);
+
+ return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+static int do_shell_var (int argc, char **argv)
+{
+ int i;
+
+ for (i = 0; i < argc; i++) {
+ char *p = argv[i];
+
+ if (i != 0)
+ putchar (' ');
+
+ while (*p) {
+ char c = *p++;
+ if (! isalnum (c))
+ c = '_';
+ putchar (c);
+ }
+ }
+ putchar ('\n');
+
+ return (EXIT_SUCCESS);
+}
+
+#ifdef __linux__
+static char *proc_getent (const char *ent)
+{
+ FILE *fp;
+ char *buffer;
+ char *p;
+ char *value = NULL;
+ int i;
+
+ if (! exists ("/proc/cmdline"))
+ return (NULL);
+
+ if (! (fp = fopen ("/proc/cmdline", "r"))) {
+ eerror ("failed to open `/proc/cmdline': %s", strerror (errno));
+ return (NULL);
+ }
+
+ buffer = xmalloc (sizeof (char) * RC_LINEBUFFER);
+ memset (buffer, 0, RC_LINEBUFFER);
+ if (fgets (buffer, RC_LINEBUFFER, fp) &&
+ (p = strstr (buffer, ent)))
+ {
+ i = p - buffer;
+ if (i == '\0' || buffer[i - 1] == ' ') {
+ /* Trim the trailing carriage return if present */
+ i = strlen (buffer) - 1;
+ if (buffer[i] == '\n')
+ buffer[i] = 0;
+
+ p += strlen (ent);
+ if (*p == '=')
+ p++;
+ value = xstrdup (strsep (&p, " "));
+ }
+ } else
+ errno = ENOENT;
+ free (buffer);
+ fclose (fp);
+
+ return (value);
+}
+#endif
+
+static char read_key (bool block)
+{
+ struct termios termios;
+ char c = 0;
+ int fd = fileno (stdin);
+
+ if (! isatty (fd))
+ return (false);
+
+ /* Now save our terminal settings. We need to restore them at exit as we
+ will be changing it for non-blocking reads for Interactive */
+ if (! termios_orig) {
+ termios_orig = xmalloc (sizeof (struct termios));
+ tcgetattr (fd, termios_orig);
+ }
+
+ tcgetattr (fd, &termios);
+ termios.c_lflag &= ~(ICANON | ECHO);
+ if (block)
+ termios.c_cc[VMIN] = 1;
+ else {
+ termios.c_cc[VMIN] = 0;
+ termios.c_cc[VTIME] = 0;
+ }
+ tcsetattr (fd, TCSANOW, &termios);
+
+ read (fd, &c, 1);
+
+ tcsetattr (fd, TCSANOW, termios_orig);
+
+ return (c);
+}
+
+static bool want_interactive (void)
+{
+ char c;
+ static bool gotinteractive;
+ static bool interactive;
+
+ if (rc_yesno (getenv ("EINFO_QUIET")))
+ return (false);
+
+ if (PREVLEVEL &&
+ strcmp (PREVLEVEL, "N") != 0 &&
+ strcmp (PREVLEVEL, "S") != 0 &&
+ strcmp (PREVLEVEL, "1") != 0)
+ return (false);
+
+ if (! gotinteractive) {
+ gotinteractive = true;
+ interactive = rc_conf_yesno ("rc_interactive");
+ }
+ if (! interactive)
+ return (false);
+
+ c = read_key (false);
+ return ((c == 'I' || c == 'i') ? true : false);
+}
+
+static void mark_interactive (void)
+{
+ FILE *fp = fopen (INTERACTIVE, "w");
+ if (fp)
+ fclose (fp);
+}
+
+static void sulogin (bool cont)
+{
+#ifdef __linux__
+ char *e = getenv ("RC_SYS");
+
+ /* VPS systems cannot do a sulogin */
+ if (e && strcmp (e, "VPS") == 0) {
+ execl ("/sbin/halt", "/sbin/halt", "-f", (char *) NULL);
+ eerrorx ("%s: unable to exec `/sbin/halt': %s", applet, strerror (errno));
+ }
+#endif
+
+ newenv = env_filter ();
+
+ if (cont) {
+ int status = 0;
+#ifdef __linux__
+ char *tty = ttyname (STDOUT_FILENO);
+#endif
+
+ pid_t pid = vfork ();
+
+ if (pid == -1)
+ eerrorx ("%s: vfork: %s", applet, strerror (errno));
+ if (pid == 0) {
+#ifdef __linux__
+ if (tty)
+ execle (SULOGIN, SULOGIN, tty, (char *) NULL, newenv);
+ else
+ execle (SULOGIN, SULOGIN, (char *) NULL, newenv);
+
+ eerror ("%s: unable to exec `%s': %s", applet, SULOGIN,
+ strerror (errno));
+#else
+ execle ("/bin/sh", "/bin/sh", (char *) NULL, newenv);
+ eerror ("%s: unable to exec `/bin/sh': %s", applet,
+ strerror (errno));
+#endif
+ _exit (EXIT_FAILURE);
+ }
+ waitpid (pid, &status, 0);
+ } else {
+ rc_logger_close ();
+
+#ifdef __linux__
+ execle ("/sbin/sulogin", "/sbin/sulogin", (char *) NULL, newenv);
+ eerrorx ("%s: unable to exec `/sbin/sulogin': %s", applet, strerror (errno));
+#else
+ exit (EXIT_SUCCESS);
+#endif
+ }
+}
+
+static void single_user (void)
+{
+ rc_logger_close ();
+
+#ifdef __linux__
+ execl ("/sbin/telinit", "/sbin/telinit", "S", (char *) NULL);
+ eerrorx ("%s: unable to exec `/sbin/telinit': %s",
+ applet, strerror (errno));
+#else
+ if (kill (1, SIGTERM) != 0)
+ eerrorx ("%s: unable to send SIGTERM to init (pid 1): %s",
+ applet, strerror (errno));
+ exit (EXIT_SUCCESS);
+#endif
+}
+
+static bool set_ksoftlevel (const char *level)
+{
+ FILE *fp;
+
+ if (! level ||
+ strcmp (level, getenv ("RC_BOOTLEVEL")) == 0 ||
+ strcmp (level, RC_LEVEL_SINGLE) == 0 ||
+ strcmp (level, RC_LEVEL_SYSINIT) == 0)
+ {
+ if (exists (RC_KSOFTLEVEL) &&
+ unlink (RC_KSOFTLEVEL) != 0)
+ eerror ("unlink `%s': %s", RC_KSOFTLEVEL, strerror (errno));
+ return (false);
+ }
+
+ if (! (fp = fopen (RC_KSOFTLEVEL, "w"))) {
+ eerror ("fopen `%s': %s", RC_KSOFTLEVEL, strerror (errno));
+ return (false);
+ }
+
+ fprintf (fp, "%s", level);
+ fclose (fp);
+ return (true);
+}
+
+static int get_ksoftlevel (char *buffer, int buffer_len)
+{
+ FILE *fp;
+ int i = 0;
+
+ if (! exists (RC_KSOFTLEVEL))
+ return (0);
+
+ if (! (fp = fopen (RC_KSOFTLEVEL, "r"))) {
+ eerror ("fopen `%s': %s", RC_KSOFTLEVEL, strerror (errno));
+ return (-1);
+ }
+
+ if (fgets (buffer, buffer_len, fp)) {
+ i = strlen (buffer) - 1;
+ if (buffer[i] == '\n')
+ buffer[i] = 0;
+ }
+
+ fclose (fp);
+ return (i);
+}
+
+static void add_pid (pid_t pid)
+{
+ pidlist_t *sp = service_pids;
+ if (sp) {
+ while (sp->next)
+ sp = sp->next;
+ sp->next = xmalloc (sizeof (pidlist_t));
+ sp = sp->next;
+ } else
+ sp = service_pids = xmalloc (sizeof (pidlist_t));
+ memset (sp, 0, sizeof (pidlist_t));
+ sp->pid = pid;
+}
+
+static void remove_pid (pid_t pid)
+{
+ pidlist_t *last = NULL;
+ pidlist_t *pl;
+
+ for (pl = service_pids; pl; pl = pl->next) {
+ if (pl->pid == pid) {
+ if (last)
+ last->next = pl->next;
+ else
+ service_pids = pl->next;
+ free (pl);
+ break;
+ }
+ last = pl;
+ }
+}
+
+static void wait_for_services ()
+{
+ while (waitpid (0, 0, 0) != -1);
+}
+
+static void handle_signal (int sig)
+{
+ int serrno = errno;
+ char signame[10] = { '\0' };
+ pidlist_t *pl;
+ pid_t pid;
+ int status = 0;
+ struct winsize ws;
+
+ switch (sig) {
+ case SIGCHLD:
+ do {
+ pid = waitpid (-1, &status, WNOHANG);
+ if (pid < 0) {
+ if (errno != ECHILD)
+ eerror ("waitpid: %s", strerror (errno));
+ return;
+ }
+ } while (! WIFEXITED (status) && ! WIFSIGNALED (status));
+
+ /* Remove that pid from our list */
+ if (pid > 0)
+ remove_pid (pid);
+ break;
+
+ case SIGWINCH:
+ if (rc_logger_tty >= 0) {
+ ioctl (STDIN_FILENO, TIOCGWINSZ, &ws);
+ ioctl (rc_logger_tty, TIOCSWINSZ, &ws);
+ }
+ break;
+
+ case SIGINT:
+ if (! signame[0])
+ snprintf (signame, sizeof (signame), "SIGINT");
+ case SIGTERM:
+ if (! signame[0])
+ snprintf (signame, sizeof (signame), "SIGTERM");
+ case SIGQUIT:
+ if (! signame[0])
+ snprintf (signame, sizeof (signame), "SIGQUIT");
+ eerrorx ("%s: caught %s, aborting", applet, signame);
+ case SIGUSR1:
+ eerror ("rc: Aborting!");
+ /* Kill any running services we have started */
+
+ signal (SIGCHLD, SIG_IGN);
+ for (pl = service_pids; pl; pl = pl->next)
+ kill (pl->pid, SIGTERM);
+
+ /* Notify plugins we are aborting */
+ rc_plugin_run (RC_HOOK_ABORT, NULL);
+
+ /* Only drop into single user mode if we're booting */
+ if ((PREVLEVEL &&
+ (strcmp (PREVLEVEL, "S") == 0 ||
+ strcmp (PREVLEVEL, "1") == 0)) ||
+ (RUNLEVEL &&
+ (strcmp (RUNLEVEL, "S") == 0 ||
+ strcmp (RUNLEVEL, "1") == 0)))
+ single_user ();
+
+ exit (EXIT_FAILURE);
+ break;
+
+ default:
+ eerror ("%s: caught unknown signal %d", applet, sig);
+ }
+
+ /* Restore errno */
+ errno = serrno;
+}
+
+static void run_script (const char *script)
+{
+ int status = 0;
+ pid_t pid = vfork ();
+
+ if (pid < 0)
+ eerrorx ("%s: vfork: %s", applet, strerror (errno));
+ else if (pid == 0) {
+ execl (script, script, (char *) NULL);
+ eerror ("%s: unable to exec `%s': %s",
+ script, applet, strerror (errno));
+ _exit (EXIT_FAILURE);
+ }
+
+ do {
+ pid_t wpid = waitpid (pid, &status, 0);
+ if (wpid < 1)
+ eerror ("waitpid: %s", strerror (errno));
+ } while (! WIFEXITED (status) && ! WIFSIGNALED (status));
+
+ if (! WIFEXITED (status) || ! WEXITSTATUS (status) == 0)
+ eerrorx ("%s: failed to exec `%s'", applet, script);
+}
+
+#include "_usage.h"
+#define getoptstring "o:" getoptstring_COMMON
+static struct option longopts[] = {
+ { "override", 1, NULL, 'o' },
+ longopts_COMMON
+};
+static const char * const longopts_help[] = {
+ "override the next runlevel to change into\nwhen leaving single user or boot runlevels",
+ longopts_help_COMMON
+};
+#include "_usage.c"
+
+int main (int argc, char **argv)
+{
+ const char *bootlevel = NULL;
+ char *newlevel = NULL;
+ char *service = NULL;
+ char **deporder = NULL;
+ char **tmplist;
+ int i = 0;
+ int j = 0;
+ bool going_down = false;
+ bool interactive = false;
+ int depoptions = RC_DEP_STRICT | RC_DEP_TRACE;
+ char ksoftbuffer [PATH_MAX];
+ char pidstr[6];
+ int opt;
+ DIR *dp;
+ struct dirent *d;
+ bool parallel;
+ int regen = 0;
+
+ applet = basename_c (argv[0]);
+ atexit (cleanup);
+ if (! applet)
+ eerrorx ("arguments required");
+
+ if (argc > 1 && (strcmp (argv[1], "--version") == 0)) {
+ printf ("%s (OpenRC"
+#ifdef BRANDING
+ " " BRANDING
+#endif
+ ") version " VERSION "\n", applet);
+ exit (EXIT_SUCCESS);
+ }
+
+ /* These used to be programs in their own right, so we shouldn't
+ * touch argc or argv for them */
+ if (strcmp (applet, "fstabinfo") == 0)
+ exit (fstabinfo (argc, argv));
+ else if (strcmp (applet, "mountinfo") == 0)
+ exit (mountinfo (argc, argv));
+ else if (strcmp (applet, "rc-depend") == 0)
+ exit (rc_depend (argc, argv));
+ else if (strcmp (applet, "rc-status") == 0)
+ exit (rc_status (argc, argv));
+ else if (strcmp (applet, "rc-update") == 0 ||
+ strcmp (applet, "update-rc") == 0)
+ exit (rc_update (argc, argv));
+ else if (strcmp (applet, "runscript") == 0)
+ exit (runscript (argc, argv));
+ else if (strcmp (applet, "start-stop-daemon") == 0)
+ exit (start_stop_daemon (argc, argv));
+ else if (strcmp (applet, "checkpath") == 0)
+ exit (checkpath (argc, argv));
+
+ argc--;
+ argv++;
+
+ /* Handle multicall stuff */
+ if (applet[0] == 'e' || (applet[0] == 'v' && applet[1] == 'e'))
+ exit (do_e (argc, argv));
+
+ if (strcmp (applet, "service_get_value") == 0 ||
+ strcmp (applet, "service_set_value") == 0 ||
+ strcmp (applet, "get_options") == 0 ||
+ strcmp (applet, "save_options") == 0)
+ exit (do_value (argc, argv));
+
+ if (strncmp (applet, "service_", strlen ("service_")) == 0)
+ exit (do_service (argc, argv));
+
+ if (strncmp (applet, "mark_service_", strlen ("mark_service_")) == 0)
+ exit (do_mark_service (argc, argv));
+
+ if (strcmp (applet, "is_runlevel_start") == 0)
+ exit (rc_runlevel_starting () ? 0 : 1);
+ else if (strcmp (applet, "is_runlevel_stop") == 0)
+ exit (rc_runlevel_stopping () ? 0 : 1);
+
+ if (strcmp (applet, "shell_var") == 0)
+ exit (do_shell_var (argc, argv));
+
+ if (strcmp (applet, "rc-abort") == 0) {
+ char *p = getenv ("RC_PID");
+ pid_t pid = 0;
+
+ if (p && sscanf (p, "%d", &pid) == 1) {
+ if (kill (pid, SIGUSR1) != 0)
+ eerrorx ("rc-abort: failed to signal parent %d: %s",
+ pid, strerror (errno));
+ exit (EXIT_SUCCESS);
+ }
+ exit (EXIT_FAILURE);
+ }
+
+ if (strcmp (applet, "rc" ) != 0)
+ eerrorx ("%s: unknown applet", applet);
+
+ /* Change dir to / to ensure all scripts don't use stuff in pwd */
+ chdir ("/");
+
+ /* RUNLEVEL is set by sysvinit as is a magic number
+ RC_SOFTLEVEL is set by us and is the name for this magic number
+ even though all our userland documentation refers to runlevel */
+ RUNLEVEL = getenv ("RUNLEVEL");
+ PREVLEVEL = getenv ("PREVLEVEL");
+
+ /* Ensure our environment is pure
+ Also, add our configuration to it */
+ env = env_filter ();
+ tmplist = env_config ();
+ rc_strlist_join (&env, tmplist);
+ rc_strlist_free (tmplist);
+
+ if (env) {
+ char *p;
+
+#ifdef __linux__
+ /* clearenv isn't portable, but there's no harm in using it
+ if we have it */
+ clearenv ();
+#else
+ char *var;
+ /* No clearenv present here then.
+ We could manipulate environ directly ourselves, but it seems that
+ some kernels bitch about this according to the environ man pages
+ so we walk though environ and call unsetenv for each value. */
+ while (environ[0]) {
+ tmp = xstrdup (environ[0]);
+ p = tmp;
+ var = strsep (&p, "=");
+ unsetenv (var);
+ free (tmp);
+ }
+ tmp = NULL;
+#endif
+
+ STRLIST_FOREACH (env, p, i)
+ if (strcmp (p, "RC_SOFTLEVEL") != 0 && strcmp (p, "SOFTLEVEL") != 0)
+ putenv (p);
+
+ /* We don't free our list as that would be null in environ */
+ }
+
+ argc++;
+ argv--;
+ while ((opt = getopt_long (argc, argv, getoptstring,
+ longopts, (int *) 0)) != -1)
+ {
+ switch (opt) {
+ case 'o':
+ if (strlen (optarg) == 0)
+ optarg = NULL;
+ exit (set_ksoftlevel (optarg) ? EXIT_SUCCESS : EXIT_FAILURE);
+ case_RC_COMMON_GETOPT
+ }
+ }
+
+ newlevel = argv[optind++];
+
+ /* OK, so we really are the main RC process
+ Only root should be able to run us */
+ if (geteuid () != 0)
+ eerrorx ("%s: root access required", applet);
+
+ /* Enable logging */
+ setenv ("EINFO_LOG", "rc", 1);
+
+ /* Export our PID */
+ snprintf (pidstr, sizeof (pidstr), "%d", getpid ());
+ setenv ("RC_PID", pidstr, 1);
+
+ /* Load current softlevel */
+ bootlevel = getenv ("RC_BOOTLEVEL");
+ runlevel = rc_runlevel_get ();
+
+ rc_logger_open (newlevel ? newlevel : runlevel);
+
+ /* Setup a signal handler */
+ signal (SIGINT, handle_signal);
+ signal (SIGQUIT, handle_signal);
+ signal (SIGTERM, handle_signal);
+ signal (SIGUSR1, handle_signal);
+ signal (SIGWINCH, handle_signal);
+
+ if (! rc_yesno (getenv ("EINFO_QUIET")))
+ interactive = exists (INTERACTIVE);
+ rc_plugin_load ();
+
+ /* Check we're in the runlevel requested, ie from
+ rc single
+ rc shutdown
+ rc reboot
+ */
+ if (newlevel) {
+ if (strcmp (newlevel, RC_LEVEL_SYSINIT) == 0 &&
+ RUNLEVEL &&
+ (strcmp (RUNLEVEL, "S") == 0 ||
+ strcmp (RUNLEVEL, "1") == 0))
+ {
+ /* OK, we're either in runlevel 1 or single user mode */
+ struct utsname uts;
+#ifdef __linux__
+ char *cmd;
+#endif
+
+ /* exec init-early.sh if it exists
+ * This should just setup the console to use the correct
+ * font. Maybe it should setup the keyboard too? */
+ if (exists (INITEARLYSH))
+ run_script (INITEARLYSH);
+
+ uname (&uts);
+ printf ("\n %sOpenRC %s" VERSION "%s is starting up %s",
+#ifdef BRANDING
+ BRANDING
+#else
+ ""
+#endif
+ "\n\n",
+ ecolor (ECOLOR_GOOD), ecolor (ECOLOR_HILITE),
+ ecolor (ECOLOR_NORMAL));
+
+ if (! rc_yesno (getenv ("EINFO_QUIET")) &&
+ rc_conf_yesno ("rc_interactive"))
+ printf ("Press %sI%s to enter interactive boot mode\n\n",
+ ecolor (ECOLOR_GOOD), ecolor (ECOLOR_NORMAL));
+
+ setenv ("RC_SOFTLEVEL", newlevel, 1);
+ rc_plugin_run (RC_HOOK_RUNLEVEL_START_IN, newlevel);
+ run_script (INITSH);
+
+#ifdef __linux__
+ /* If we requested a softlevel, save it now */
+ set_ksoftlevel (NULL);
+ if ((cmd = proc_getent ("softlevel"))) {
+ set_ksoftlevel (cmd);
+ free (cmd);
+ }
+#endif
+
+ rc_plugin_run (RC_HOOK_RUNLEVEL_START_OUT, newlevel);
+
+ if (want_interactive ())
+ mark_interactive ();
+
+ exit (EXIT_SUCCESS);
+ } else if (strcmp (newlevel, RC_LEVEL_SINGLE) == 0) {
+ if (! RUNLEVEL ||
+ (strcmp (RUNLEVEL, "S") != 0 &&
+ strcmp (RUNLEVEL, "1") != 0))
+ {
+ /* Remember the current runlevel for when we come back */
+ set_ksoftlevel (runlevel);
+ single_user ();
+ }
+ } else if (strcmp (newlevel, RC_LEVEL_REBOOT) == 0) {
+ if (! RUNLEVEL ||
+ strcmp (RUNLEVEL, "6") != 0)
+ {
+ rc_logger_close ();
+ execl (SHUTDOWN, SHUTDOWN, "-r", "now", (char *) NULL);
+ eerrorx ("%s: unable to exec `" SHUTDOWN "': %s",
+ applet, strerror (errno));
+ }
+ } else if (strcmp (newlevel, RC_LEVEL_SHUTDOWN) == 0) {
+ if (! RUNLEVEL ||
+ strcmp (RUNLEVEL, "0") != 0)
+ {
+ rc_logger_close ();
+ execl (SHUTDOWN, SHUTDOWN,
+#ifdef __linux__
+ "-h",
+#else
+ "-p",
+#endif
+ "now", (char *) NULL);
+ eerrorx ("%s: unable to exec `" SHUTDOWN "': %s",
+ applet, strerror (errno));
+ }
+ }
+ }
+
+ /* Now we start handling our children */
+ signal (SIGCHLD, handle_signal);
+
+ /* We should only use ksoftlevel if we were in single user mode
+ If not, we need to erase ksoftlevel now. */
+ if (PREVLEVEL &&
+ (strcmp (PREVLEVEL, "1") == 0 ||
+ strcmp (PREVLEVEL, "S") == 0 ||
+ strcmp (PREVLEVEL, "N") == 0))
+ {
+ /* Try not to join boot and ksoftlevels together */
+ if (! newlevel ||
+ strcmp (newlevel, getenv ("RC_BOOTLEVEL")) != 0)
+ if (get_ksoftlevel (ksoftbuffer, sizeof (ksoftbuffer)))
+ newlevel = ksoftbuffer;
+ } else if (! RUNLEVEL ||
+ (strcmp (RUNLEVEL, "1") != 0 &&
+ strcmp (RUNLEVEL, "S") != 0 &&
+ strcmp (RUNLEVEL, "N") != 0))
+ {
+ set_ksoftlevel (NULL);
+ }
+
+ if (newlevel &&
+ (strcmp (newlevel, RC_LEVEL_REBOOT) == 0 ||
+ strcmp (newlevel, RC_LEVEL_SHUTDOWN) == 0 ||
+ strcmp (newlevel, RC_LEVEL_SINGLE) == 0))
+ {
+ going_down = true;
+ rc_runlevel_set (newlevel);
+ setenv ("RC_SOFTLEVEL", newlevel, 1);
+
+#ifdef __FreeBSD__
+ /* FIXME: we shouldn't have todo this */
+ /* For some reason, wait_for_services waits for the logger proccess
+ * to finish as well, but only on FreeBSD. We cannot allow this so
+ * we stop logging now. */
+ rc_logger_close ();
+#endif
+
+ rc_plugin_run (RC_HOOK_RUNLEVEL_STOP_IN, newlevel);
+ } else {
+ rc_plugin_run (RC_HOOK_RUNLEVEL_STOP_IN, runlevel);
+ }
+
+ /* Check if runlevel is valid if we're changing */
+ if (newlevel && strcmp (runlevel, newlevel) != 0 && ! going_down) {
+ if (! rc_runlevel_exists (newlevel))
+ eerrorx ("%s: is not a valid runlevel", newlevel);
+ }
+
+ /* Load our deptree now */
+ if ((deptree = _rc_deptree_load (&regen)) == NULL)
+ eerrorx ("failed to load deptree");
+
+ /* Clean the failed services state dir now */
+ clean_failed ();
+
+ mkdir (RC_STOPPING, 0755);
+
+#ifdef __linux__
+ /* udev likes to start services before we're ready when it does
+ its coldplugging thing. runscript knows when we're not ready so it
+ stores a list of coldplugged services in DEVBOOT for us to pick up
+ here when we are ready for them */
+ if ((dp = opendir (DEVBOOT))) {
+ while ((d = readdir (dp))) {
+ if (d->d_name[0] == '.' &&
+ (d->d_name[1] == '\0' ||
+ (d->d_name[1] == '.' && d->d_name[2] == '\0')))
+ continue;
+
+ if (rc_service_exists (d->d_name) &&
+ rc_service_plugable (d->d_name))
+ rc_service_mark (d->d_name, RC_SERVICE_COLDPLUGGED);
+
+ i = strlen (DEVBOOT "/") + strlen (d->d_name) + 1;
+ tmp = xmalloc (sizeof (char) * i);
+ snprintf (tmp, i, DEVBOOT "/%s", d->d_name);
+ if (tmp) {
+ if (unlink (tmp))
+ eerror ("%s: unlink `%s': %s", applet, tmp,
+ strerror (errno));
+ free (tmp);
+ }
+ }
+ closedir (dp);
+ rmdir (DEVBOOT);
+ }
+#else
+ /* BSD's on the other hand populate /dev automagically and use devd.
+ The only downside of this approach and ours is that we have to hard code
+ the device node to the init script to simulate the coldplug into
+ runlevel for our dependency tree to work. */
+ if (newlevel && strcmp (newlevel, bootlevel) == 0 &&
+ (strcmp (runlevel, RC_LEVEL_SINGLE) == 0 ||
+ strcmp (runlevel, RC_LEVEL_SYSINIT) == 0) &&
+ rc_conf_yesno ("rc_coldplug"))
+ {
+#if defined(__DragonFly__) || defined(__FreeBSD__)
+ /* The net interfaces are easy - they're all in net /dev/net :) */
+ if ((dp = opendir ("/dev/net"))) {
+ while ((d = readdir (dp))) {
+ i = (strlen ("net.") + strlen (d->d_name) + 1);
+ tmp = xmalloc (sizeof (char) * i);
+ snprintf (tmp, i, "net.%s", d->d_name);
+ if (rc_service_exists (tmp) &&
+ rc_service_plugable (tmp))
+ rc_service_mark (tmp, RC_SERVICE_COLDPLUGGED);
+ CHAR_FREE (tmp);
+ }
+ closedir (dp);
+ }
+#endif
+
+ /* The mice are a little more tricky.
+ If we coldplug anything else, we'll probably do it here. */
+ if ((dp == opendir ("/dev"))) {
+ while ((d = readdir (dp))) {
+ if (strncmp (d->d_name, "psm", 3) == 0 ||
+ strncmp (d->d_name, "ums", 3) == 0)
+ {
+ char *p = d->d_name + 3;
+ if (p && isdigit (*p)) {
+ i = (strlen ("moused.") + strlen (d->d_name) + 1);
+ tmp = xmalloc (sizeof (char) * i);
+ snprintf (tmp, i, "moused.%s", d->d_name);
+ if (rc_service_exists (tmp) &&
+ rc_service_plugable (tmp))
+ rc_service_mark (tmp, RC_SERVICE_COLDPLUGGED);
+ CHAR_FREE (tmp);
+ }
+ }
+ }
+ closedir (dp);
+ }
+ }
+#endif
+
+ /* Build a list of all services to stop and then work out the
+ correct order for stopping them */
+ stop_services = rc_services_in_state (RC_SERVICE_STARTING);
+
+ tmplist = rc_services_in_state (RC_SERVICE_INACTIVE);
+ rc_strlist_join (&stop_services, tmplist);
+ rc_strlist_free (tmplist);
+
+ tmplist = rc_services_in_state (RC_SERVICE_STARTED);
+ rc_strlist_join (&stop_services, tmplist);
+ rc_strlist_free (tmplist);
+
+ deporder = rc_deptree_depends (deptree, types_nua,
+ (const char **) stop_services,
+ runlevel, depoptions | RC_DEP_STOP);
+
+ rc_strlist_free (stop_services);
+ stop_services = deporder;
+ deporder = NULL;
+ rc_strlist_reverse (stop_services);
+
+ /* Load our list of coldplugged services */
+ coldplugged_services = rc_services_in_state (RC_SERVICE_COLDPLUGGED);
+
+ /* Load our start services now.
+ We have different rules dependent on runlevel. */
+ if (newlevel && strcmp (newlevel, bootlevel) == 0) {
+ if (coldplugged_services) {
+ bool quiet = rc_yesno (getenv ("EINFO_QUIET"));
+
+ if (! quiet)
+ einfon ("Device initiated services:");
+ STRLIST_FOREACH (coldplugged_services, service, i) {
+ if (! quiet)
+ printf (" %s", service);
+ rc_strlist_add (&start_services, service);
+ }
+ if (! quiet)
+ printf ("\n");
+ }
+ tmplist = rc_services_in_runlevel (newlevel ? newlevel : runlevel);
+ rc_strlist_join (&start_services, tmplist);
+ rc_strlist_free (tmplist);
+ } else {
+ /* Store our list of coldplugged services */
+ tmplist = rc_services_in_state (RC_SERVICE_COLDPLUGGED);
+ rc_strlist_join (&coldplugged_services, tmplist);
+ rc_strlist_free (tmplist);
+ if (strcmp (newlevel ? newlevel : runlevel, RC_LEVEL_SINGLE) != 0 &&
+ strcmp (newlevel ? newlevel : runlevel, RC_LEVEL_SHUTDOWN) != 0 &&
+ strcmp (newlevel ? newlevel : runlevel, RC_LEVEL_REBOOT) != 0)
+ {
+ /* We need to include the boot runlevel services if we're not in it */
+ tmplist = rc_services_in_runlevel (bootlevel);
+ rc_strlist_join (&start_services, tmplist);
+ rc_strlist_free (tmplist);
+ tmplist = rc_services_in_runlevel (newlevel ? newlevel : runlevel);
+ rc_strlist_join (&start_services, tmplist);
+ rc_strlist_free (tmplist);
+
+ STRLIST_FOREACH (coldplugged_services, service, i)
+ rc_strlist_add (&start_services, service);
+
+ }
+ }
+
+ /* Save out softlevel now */
+ if (going_down)
+ rc_runlevel_set (newlevel);
+
+ parallel = rc_conf_yesno ("rc_parallel");
+
+ /* Now stop the services that shouldn't be running */
+ STRLIST_FOREACH (stop_services, service, i) {
+ bool found = false;
+ char *conf = NULL;
+ char **stopdeps = NULL;
+ char *svc1 = NULL;
+ char *svc2 = NULL;
+ int k;
+
+ if (rc_service_state (service) & RC_SERVICE_STOPPED)
+ continue;
+
+ /* We always stop the service when in these runlevels */
+ if (going_down) {
+ pid_t pid = rc_service_stop (service);
+ if (pid > 0 && ! parallel)
+ rc_waitpid (pid);
+ continue;
+ }
+
+ /* If we're in the start list then don't bother stopping us */
+ STRLIST_FOREACH (start_services, svc1, j)
+ if (strcmp (svc1, service) == 0) {
+ found = true;
+ break;
+ }
+
+ /* Unless we would use a different config file */
+ if (found) {
+ int len;
+ if (! newlevel)
+ continue;
+
+ len = strlen (service) + strlen (runlevel) + 2;
+ tmp = xmalloc (sizeof (char) * len);
+ snprintf (tmp, len, "%s.%s", service, runlevel);
+ conf = rc_strcatpaths (RC_CONFDIR, tmp, (char *) NULL);
+ found = exists (conf);
+ CHAR_FREE (conf);
+ CHAR_FREE (tmp);
+ if (! found) {
+ len = strlen (service) + strlen (newlevel) + 2;
+ tmp = xmalloc (sizeof (char) * len);
+ snprintf (tmp, len, "%s.%s", service, newlevel);
+ conf = rc_strcatpaths (RC_CONFDIR, tmp, (char *) NULL);
+ found = exists (conf);
+ CHAR_FREE (conf);
+ CHAR_FREE (tmp);
+ if (!found)
+ continue;
+ }
+ } else {
+ /* Allow coldplugged services not to be in the runlevels list */
+ if (rc_service_state (service) & RC_SERVICE_COLDPLUGGED)
+ continue;
+ }
+
+ /* We got this far! Or last check is to see if any any service that
+ going to be started depends on us */
+ rc_strlist_add (&stopdeps, service);
+ deporder = rc_deptree_depends (deptree, types_n,
+ (const char **) stopdeps,
+ runlevel, RC_DEP_STRICT);
+ rc_strlist_free (stopdeps);
+ stopdeps = NULL;
+ found = false;
+ STRLIST_FOREACH (deporder, svc1, j) {
+ STRLIST_FOREACH (start_services, svc2, k)
+ if (strcmp (svc1, svc2) == 0) {
+ found = true;
+ break;
+ }
+ if (found)
+ break;
+ }
+ rc_strlist_free (deporder);
+ deporder = NULL;
+
+ /* After all that we can finally stop the blighter! */
+ if (! found) {
+ pid_t pid;
+
+ if ((pid = rc_service_stop (service)) > 0) {
+ add_pid (pid);
+
+ if (! parallel) {
+ rc_waitpid (pid);
+ remove_pid (pid);
+ }
+ }
+ }
+ }
+
+ /* Wait for our services to finish */
+ wait_for_services ();
+
+ /* Notify the plugins we have finished */
+ rc_plugin_run (RC_HOOK_RUNLEVEL_STOP_OUT, runlevel);
+
+ rmdir (RC_STOPPING);
+
+ /* Store the new runlevel */
+ if (newlevel) {
+ rc_runlevel_set (newlevel);
+ free (runlevel);
+ runlevel = xstrdup (newlevel);
+ setenv ("RC_SOFTLEVEL", runlevel, 1);
+ }
+
+ /* Run the halt script if needed */
+ if (strcmp (runlevel, RC_LEVEL_SHUTDOWN) == 0 ||
+ strcmp (runlevel, RC_LEVEL_REBOOT) == 0)
+ {
+ rc_logger_close ();
+ execl (HALTSH, HALTSH, runlevel, (char *) NULL);
+ eerrorx ("%s: unable to exec `%s': %s",
+ applet, HALTSH, strerror (errno));
+ }
+
+ /* Single user is done now */
+ if (strcmp (runlevel, RC_LEVEL_SINGLE) == 0) {
+ if (exists (INTERACTIVE))
+ unlink (INTERACTIVE);
+ sulogin (false);
+ }
+
+ mkdir (RC_STARTING, 0755);
+ rc_plugin_run (RC_HOOK_RUNLEVEL_START_IN, runlevel);
+
+ /* Re-add our coldplugged services if they stopped */
+ STRLIST_FOREACH (coldplugged_services, service, i)
+ rc_service_mark (service, RC_SERVICE_COLDPLUGGED);
+
+ /* Order the services to start */
+ deporder = rc_deptree_depends (deptree, types_nua,
+ (const char **) start_services,
+ runlevel, depoptions | RC_DEP_START);
+ rc_strlist_free (start_services);
+ start_services = deporder;
+ deporder = NULL;
+
+#ifdef __linux__
+ /* mark any services skipped as started */
+ if (PREVLEVEL && strcmp (PREVLEVEL, "N") == 0) {
+ if ((service = proc_getent ("noinitd"))) {
+ char *p = service;
+ char *token;
+
+ while ((token = strsep (&p, ",")))
+ rc_service_mark (token, RC_SERVICE_STARTED);
+ free (service);
+ }
+ }
+#endif
+
+ STRLIST_FOREACH (start_services, service, i) {
+ if (rc_service_state (service) & RC_SERVICE_STOPPED) {
+ pid_t pid;
+
+ if (! interactive)
+ interactive = want_interactive ();
+
+ if (interactive) {
+interactive_retry:
+ printf ("\n");
+ einfo ("About to start the service %s", service);
+ eindent ();
+ einfo ("1) Start the service\t\t2) Skip the service");
+ einfo ("3) Continue boot process\t\t4) Exit to shell");
+ eoutdent ();
+interactive_option:
+ switch (read_key (true)) {
+ case '1': break;
+ case '2': continue;
+ case '3': interactive = false; break;
+ case '4': sulogin (true); goto interactive_retry;
+ default: goto interactive_option;
+ }
+ }
+
+ /* Remember the pid if we're running in parallel */
+ if ((pid = rc_service_start (service)) > 0) {
+ add_pid (pid);
+
+ if (! parallel) {
+ rc_waitpid (pid);
+ remove_pid (pid);
+ }
+ }
+ }
+ }
+
+ /* Wait for our services to finish */
+ wait_for_services ();
+
+ rc_plugin_run (RC_HOOK_RUNLEVEL_START_OUT, runlevel);
+
+#ifdef __linux__
+ /* mark any services skipped as stopped */
+ if (PREVLEVEL && strcmp (PREVLEVEL, "N") == 0) {
+ if ((service = proc_getent ("noinitd"))) {
+ char *p = service;
+ char *token;
+
+ while ((token = strsep (&p, ",")))
+ rc_service_mark (token, RC_SERVICE_STOPPED);
+ free (service);
+ }
+ }
+#endif
+
+ /* Store our interactive status for boot */
+ if (interactive && strcmp (runlevel, bootlevel) == 0)
+ mark_interactive ();
+ else {
+ if (exists (INTERACTIVE))
+ unlink (INTERACTIVE);
+ }
+
+ /* If we're in the boot runlevel and we regenerated our dependencies
+ * we need to delete them so that they are regenerated again in the
+ * default runlevel as they may depend on things that are now available */
+ if (regen && strcmp (runlevel, bootlevel) == 0)
+ unlink (RC_DEPTREE);
+
+ return (EXIT_SUCCESS);
+}
+
diff --git a/src/rc/rc.map b/src/rc/rc.map
new file mode 100644
index 0000000..e5f8ee3
--- /dev/null
+++ b/src/rc/rc.map
@@ -0,0 +1,58 @@
+RC_1.0 {
+global:
+ rc_config_list;
+ rc_config_load;
+ rc_config_value;
+ rc_deptree_depend;
+ rc_deptree_depends;
+ rc_deptree_free;
+ rc_deptree_load;
+ rc_deptree_order;
+ rc_deptree_update;
+ rc_deptree_update_needed;
+ rc_environ_fd;
+ rc_find_pids;
+ rc_runlevel_exists;
+ rc_runlevel_get;
+ rc_runlevel_list;
+ rc_runlevel_set;
+ rc_runlevel_starting;
+ rc_runlevel_stopping;
+ rc_service_add;
+ rc_service_daemons_crashed;
+ rc_service_daemon_set;
+ rc_service_delete;
+ rc_service_description;
+ rc_service_exists;
+ rc_service_in_runlevel;
+ rc_service_mark;
+ rc_service_options;
+ rc_service_plugable;
+ rc_service_resolve;
+ rc_service_schedule_clear;
+ rc_service_schedule_start;
+ rc_service_start;
+ rc_service_stop;
+ rc_services_in_runlevel;
+ rc_services_in_state;
+ rc_services_scheduled;
+ rc_services_scheduled_by;
+ rc_service_started_daemon;
+ rc_service_state;
+ rc_service_value_get;
+ rc_service_value_set;
+ rc_strcatpaths;
+ rc_strlist_add;
+ rc_strlist_addu;
+ rc_strlist_addsort;
+ rc_strlist_addsortc;
+ rc_strlist_addsortu;
+ rc_strlist_delete;
+ rc_strlist_free;
+ rc_strlist_join;
+ rc_strlist_reverse;
+ rc_yesno;
+
+local:
+ *;
+};
diff --git a/src/rc/runscript.c b/src/rc/runscript.c
new file mode 100644
index 0000000..1385bb0
--- /dev/null
+++ b/src/rc/runscript.c
@@ -0,0 +1,1311 @@
+/*
+ * runscript.c
+ * Handle launching of init scripts.
+ */
+
+/*
+ * Copyright 2007 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.
+ */
+
+#include <sys/select.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifdef __linux__
+# include <pty.h>
+#else
+# include <libutil.h>
+#endif
+
+#include "builtins.h"
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "rc-plugin.h"
+#include "strlist.h"
+
+#define SELINUX_LIB RC_LIBDIR "/runscript_selinux.so"
+
+#define PREFIX_LOCK RC_SVCDIR "/prefix.lock"
+
+/* usecs to wait while we poll the fifo */
+#define WAIT_INTERVAL 20000000
+
+/* max secs to wait until a service comes up */
+#define WAIT_MAX 300
+
+#define ONE_SECOND 1000000000
+
+static const char *applet = NULL;
+static char *service = NULL;
+static char *exclusive = NULL;
+static char *mtime_test = NULL;
+static rc_depinfo_t *deptree = NULL;
+static char **services = NULL;
+static char **tmplist = NULL;
+static char **providelist = NULL;
+static char **restart_services = NULL;
+static char **need_services = NULL;
+static char **use_services = NULL;
+static char **env = NULL;
+static char *tmp = NULL;
+static char *softlevel = NULL;
+static bool sighup = false;
+static char *ibsave = NULL;
+static bool in_background = false;
+static rc_hook_t hook_out = 0;
+static pid_t service_pid = 0;
+static char *prefix = NULL;
+static bool prefix_locked = false;
+static int signal_pipe[2] = { -1, -1 };
+static int master_tty = -1;
+
+extern char **environ;
+
+static const char *const types_b[] = { "broken", NULL };
+static const char *const types_n[] = { "ineed", NULL };
+static const char *const types_nu[] = { "ineed", "iuse", NULL };
+static const char *const types_nua[] = { "ineed", "iuse", "iafter", NULL };
+
+static const char *const types_m[] = { "needsme", NULL };
+static const char *const types_mua[] = { "needsme", "usesme", "beforeme", NULL };
+
+#ifdef __linux__
+static void (*selinux_run_init_old) (void);
+static void (*selinux_run_init_new) (int argc, char **argv);
+
+static void setup_selinux (int argc, char **argv);
+
+static void setup_selinux (int argc, char **argv)
+{
+ void *lib_handle = NULL;
+
+ if (! exists (SELINUX_LIB))
+ return;
+
+ lib_handle = dlopen (SELINUX_LIB, RTLD_NOW | RTLD_GLOBAL);
+ if (! lib_handle) {
+ eerror ("dlopen: %s", dlerror ());
+ return;
+ }
+
+ selinux_run_init_old = (void (*)(void))
+ dlfunc (lib_handle, "selinux_runscript");
+ selinux_run_init_new = (void (*)(int, char **))
+ dlfunc (lib_handle, "selinux_runscript2");
+
+ /* Use new run_init if it exists, else fall back to old */
+ if (selinux_run_init_new)
+ selinux_run_init_new (argc, argv);
+ else if (selinux_run_init_old)
+ selinux_run_init_old ();
+ else
+ /* This shouldnt happen... probably corrupt lib */
+ eerrorx ("run_init is missing from runscript_selinux.so!");
+
+ dlclose (lib_handle);
+}
+#endif
+
+static void handle_signal (int sig)
+{
+ int serrno = errno;
+ char signame[10] = { '\0' };
+ struct winsize ws;
+
+ switch (sig) {
+ case SIGHUP:
+ sighup = true;
+ break;
+
+ case SIGCHLD:
+ if (signal_pipe[1] > -1) {
+ if (write (signal_pipe[1], &sig, sizeof (sig)) == -1)
+ eerror ("%s: send: %s", service, strerror (errno));
+ } else
+ rc_waitpid (-1);
+ break;
+
+ case SIGWINCH:
+ if (master_tty >= 0) {
+ ioctl (fileno (stdout), TIOCGWINSZ, &ws);
+ ioctl (master_tty, TIOCSWINSZ, &ws);
+ }
+ break;
+
+ case SIGINT:
+ if (! signame[0])
+ snprintf (signame, sizeof (signame), "SIGINT");
+ case SIGTERM:
+ if (! signame[0])
+ snprintf (signame, sizeof (signame), "SIGTERM");
+ case SIGQUIT:
+ if (! signame[0])
+ snprintf (signame, sizeof (signame), "SIGQUIT");
+ /* Send the signal to our children too */
+ if (service_pid > 0)
+ kill (service_pid, sig);
+ eerrorx ("%s: caught %s, aborting", applet, signame);
+
+ default:
+ eerror ("%s: caught unknown signal %d", applet, sig);
+ }
+
+ /* Restore errno */
+ errno = serrno;
+}
+
+static time_t get_mtime (const char *pathname, bool follow_link)
+{
+ struct stat buf;
+ int retval;
+
+ if (! pathname)
+ return (0);
+
+ retval = follow_link ? stat (pathname, &buf) : lstat (pathname, &buf);
+ if (! retval)
+ return (buf.st_mtime);
+
+ errno = 0;
+ return (0);
+}
+
+static bool in_control ()
+{
+ char *path;
+ time_t mtime;
+ const char *tests[] = { "starting", "started", "stopping",
+ "inactive", "wasinactive", NULL };
+ int i = 0;
+
+ if (sighup)
+ return (false);
+
+ if (! mtime_test || ! exists (mtime_test))
+ return (false);
+
+ if (rc_service_state (applet) & RC_SERVICE_STOPPED)
+ return (false);
+
+ if (! (mtime = get_mtime (mtime_test, false)))
+ return (false);
+
+ while (tests[i]) {
+ path = rc_strcatpaths (RC_SVCDIR, tests[i], applet, (char *) NULL);
+ if (exists (path)) {
+ time_t m = get_mtime (path, false);
+ if (mtime < m && m != 0) {
+ free (path);
+ return (false);
+ }
+ }
+ free (path);
+ i++;
+ }
+
+ return (true);
+}
+
+static void uncoldplug ()
+{
+ char *cold = rc_strcatpaths (RC_SVCDIR, "coldplugged", applet, (char *) NULL);
+ if (exists (cold) && unlink (cold) != 0)
+ eerror ("%s: unlink `%s': %s", applet, cold, strerror (errno));
+ free (cold);
+}
+
+static void start_services (char **list) {
+ char *svc;
+ int i;
+ rc_service_state_t state = rc_service_state (service);
+
+ if (! list)
+ return;
+
+ if (state & RC_SERVICE_INACTIVE ||
+ state & RC_SERVICE_WASINACTIVE ||
+ state & RC_SERVICE_STARTING ||
+ state & RC_SERVICE_STARTED)
+ {
+ STRLIST_FOREACH (list, svc, i) {
+ if (rc_service_state (svc) & RC_SERVICE_STOPPED) {
+ if (state & RC_SERVICE_INACTIVE ||
+ state & RC_SERVICE_WASINACTIVE)
+ {
+ rc_service_schedule_start (service, svc);
+ ewarn ("WARNING: %s is scheduled to started when %s has started",
+ svc, applet);
+ } else
+ rc_service_start (svc);
+ }
+ }
+ }
+}
+
+static void restore_state (void)
+{
+ rc_service_state_t state;
+
+ if (rc_in_plugin || ! in_control ())
+ return;
+
+ state = rc_service_state (applet);
+ if (state & RC_SERVICE_STOPPING) {
+
+ if (state & RC_SERVICE_WASINACTIVE)
+ rc_service_mark (applet, RC_SERVICE_INACTIVE);
+ else
+ rc_service_mark (applet, RC_SERVICE_STARTED);
+ if (rc_runlevel_stopping ())
+ rc_service_mark (applet, RC_SERVICE_FAILED);
+ } else if (state & RC_SERVICE_STARTING) {
+ if (state & RC_SERVICE_WASINACTIVE)
+ rc_service_mark (applet, RC_SERVICE_INACTIVE);
+ else
+ rc_service_mark (applet, RC_SERVICE_STOPPED);
+ if (rc_runlevel_starting ())
+ rc_service_mark (applet, RC_SERVICE_FAILED);
+ }
+
+ if (exclusive)
+ unlink (exclusive);
+ free (exclusive);
+ exclusive = NULL;
+}
+
+static void cleanup (void)
+{
+ restore_state ();
+
+ if (! rc_in_plugin) {
+ if (prefix_locked)
+ unlink (PREFIX_LOCK);
+ if (hook_out) {
+ rc_plugin_run (hook_out, applet);
+ if (hook_out == RC_HOOK_SERVICE_START_DONE)
+ rc_plugin_run (RC_HOOK_SERVICE_START_OUT, applet);
+ else if (hook_out == RC_HOOK_SERVICE_STOP_DONE)
+ rc_plugin_run (RC_HOOK_SERVICE_STOP_OUT, applet);
+ }
+
+ if (restart_services)
+ start_services (restart_services);
+ }
+
+ rc_plugin_unload ();
+ rc_deptree_free (deptree);
+ rc_strlist_free (services);
+ rc_strlist_free (providelist);
+ rc_strlist_free (need_services);
+ rc_strlist_free (use_services);
+ rc_strlist_free (restart_services);
+ rc_strlist_free (tmplist);
+ free (ibsave);
+
+ rc_strlist_free (env);
+
+ if (mtime_test)
+ {
+ if (! rc_in_plugin)
+ unlink (mtime_test);
+ free (mtime_test);
+ }
+ free (exclusive);
+ free (service);
+ free (prefix);
+ free (softlevel);
+}
+
+static int write_prefix (const char *buffer, size_t bytes, bool *prefixed) {
+ unsigned int i;
+ const char *ec = ecolor (ECOLOR_HILITE);
+ const char *ec_normal = ecolor (ECOLOR_NORMAL);
+ ssize_t ret = 0;
+ int fd = fileno (stdout);
+
+ for (i = 0; i < bytes; i++) {
+ /* We don't prefix escape codes, like eend */
+ if (buffer[i] == '\033')
+ *prefixed = true;
+
+ if (! *prefixed) {
+ ret += write (fd, ec, strlen (ec));
+ ret += write (fd, prefix, strlen (prefix));
+ ret += write (fd, ec_normal, strlen (ec_normal));
+ ret += write (fd, "|", 1);
+ *prefixed = true;
+ }
+
+ if (buffer[i] == '\n')
+ *prefixed = false;
+ ret += write (fd, buffer + i, 1);
+ }
+
+ return (ret);
+}
+
+static bool svc_exec (const char *arg1, const char *arg2)
+{
+ bool execok;
+ int fdout = fileno (stdout);
+ struct termios tt;
+ struct winsize ws;
+ int i;
+ int flags = 0;
+ fd_set rset;
+ int s;
+ char *buffer;
+ size_t bytes;
+ bool prefixed = false;
+ int selfd;
+ int slave_tty;
+
+ /* Setup our signal pipe */
+ if (pipe (signal_pipe) == -1)
+ eerrorx ("%s: pipe: %s", service, applet);
+ for (i = 0; i < 2; i++)
+ if ((flags = fcntl (signal_pipe[i], F_GETFD, 0) == -1 ||
+ fcntl (signal_pipe[i], F_SETFD, flags | FD_CLOEXEC) == -1))
+ eerrorx ("%s: fcntl: %s", service, strerror (errno));
+
+ /* Open a pty for our prefixed output
+ * We do this instead of mapping pipes to stdout, stderr so that
+ * programs can tell if they're attached to a tty or not.
+ * The only loss is that we can no longer tell the difference
+ * between the childs stdout or stderr */
+ master_tty = slave_tty = -1;
+ if (prefix && isatty (fdout)) {
+ tcgetattr (fdout, &tt);
+ ioctl (fdout, TIOCGWINSZ, &ws);
+
+ /* If the below call fails due to not enough ptys then we don't
+ * prefix the output, but we still work */
+ openpty (&master_tty, &slave_tty, NULL, &tt, &ws);
+ }
+
+ service_pid = vfork();
+ if (service_pid == -1)
+ eerrorx ("%s: vfork: %s", service, strerror (errno));
+ if (service_pid == 0) {
+ if (slave_tty >= 0) {
+ /* Hmmm, this shouldn't work in a vfork, but it does which is
+ * good for us */
+ close (master_tty);
+
+ dup2 (slave_tty, 1);
+ dup2 (slave_tty, 2);
+ if (slave_tty > 2)
+ close (slave_tty);
+ }
+
+ if (exists (RC_SVCDIR "/runscript.sh")) {
+ execl (RC_SVCDIR "/runscript.sh", RC_SVCDIR "/runscript.sh",
+ service, arg1, arg2, (char *) NULL);
+ eerror ("%s: exec `" RC_SVCDIR "/runscript.sh': %s",
+ service, strerror (errno));
+ _exit (EXIT_FAILURE);
+ } else {
+ execl (RC_LIBDIR "/sh/runscript.sh", RC_LIBDIR "/sh/runscript.sh",
+ service, arg1, arg2, (char *) NULL);
+ eerror ("%s: exec `" RC_LIBDIR "/sh/runscript.sh': %s",
+ service, strerror (errno));
+ _exit (EXIT_FAILURE);
+ }
+ }
+
+ selfd = MAX (master_tty, signal_pipe[0]) + 1;
+ buffer = xmalloc (sizeof (char) * RC_LINEBUFFER);
+ while (1) {
+ FD_ZERO (&rset);
+ FD_SET (signal_pipe[0], &rset);
+ if (master_tty >= 0)
+ FD_SET (master_tty, &rset);
+
+ if ((s = select (selfd, &rset, NULL, NULL, NULL)) == -1) {
+ if (errno != EINTR) {
+ eerror ("%s: select: %s", service, strerror (errno));
+ break;
+ }
+ }
+
+ if (s > 0) {
+ if (master_tty >= 0 && FD_ISSET (master_tty, &rset)) {
+ bytes = read (master_tty, buffer, RC_LINEBUFFER);
+ write_prefix (buffer, bytes, &prefixed);
+ }
+
+ /* Only SIGCHLD signals come down this pipe */
+ if (FD_ISSET (signal_pipe[0], &rset))
+ break;
+ }
+ }
+
+ free (buffer);
+ close (signal_pipe[0]);
+ close (signal_pipe[1]);
+ signal_pipe[0] = signal_pipe[1] = -1;
+
+ if (master_tty >= 0) {
+ signal (SIGWINCH, SIG_IGN);
+ close (master_tty);
+ master_tty = -1;
+ }
+
+ execok = rc_waitpid (service_pid) == 0 ? true : false;
+ service_pid = 0;
+
+ return (execok);
+}
+
+static bool svc_wait (rc_depinfo_t *depinfo, const char *svc)
+{
+ char *s;
+ char *fifo;
+ struct timespec ts;
+ int nloops = WAIT_MAX * (ONE_SECOND / WAIT_INTERVAL);
+ bool retval = false;
+ bool forever = false;
+ char **keywords = NULL;
+ int i;
+
+ if (! service)
+ return (false);
+
+ /* Some services don't have a timeout, like checkroot and checkfs */
+ keywords = rc_deptree_depend (depinfo, svc, "keywords");
+ STRLIST_FOREACH (keywords, s, i) {
+ if (strcmp (s, "notimeout") == 0) {
+ forever = true;
+ break;
+ }
+ }
+ rc_strlist_free (keywords);
+
+ fifo = rc_strcatpaths (RC_SVCDIR, "exclusive", basename_c (svc), (char *) NULL);
+ ts.tv_sec = 0;
+ ts.tv_nsec = WAIT_INTERVAL;
+
+ while (nloops) {
+ if (! exists (fifo)) {
+ retval = true;
+ break;
+ }
+
+ if (nanosleep (&ts, NULL) == -1) {
+ if (errno != EINTR)
+ break;
+ }
+
+ if (! forever)
+ nloops --;
+ }
+
+ if (! exists (fifo))
+ retval = true;
+ free (fifo);
+ return (retval);
+}
+
+static rc_service_state_t svc_status ()
+{
+ char status[10];
+ int (*e) (const char *fmt, ...) = &einfo;
+
+ rc_service_state_t state = rc_service_state (service);
+
+ if (state & RC_SERVICE_STOPPING) {
+ snprintf (status, sizeof (status), "stopping");
+ e = &ewarn;
+ } else if (state & RC_SERVICE_STARTING) {
+ snprintf (status, sizeof (status), "starting");
+ e = &ewarn;
+ } else if (state & RC_SERVICE_INACTIVE) {
+ snprintf (status, sizeof (status), "inactive");
+ e = &ewarn;
+ } else if (state & RC_SERVICE_STARTED) {
+ if (geteuid () == 0 && rc_service_daemons_crashed (service)) {
+ snprintf (status, sizeof (status), "crashed");
+ e = &eerror;
+ } else
+ snprintf (status, sizeof (status), "started");
+ } else
+ snprintf (status, sizeof (status), "stopped");
+
+ e ("status: %s", status);
+ return (state);
+}
+
+static void make_exclusive ()
+{
+ char *path;
+ int i;
+
+ /* We create a fifo so that other services can wait until we complete */
+ if (! exclusive)
+ exclusive = rc_strcatpaths (RC_SVCDIR, "exclusive", applet, (char *) NULL);
+
+ if (mkfifo (exclusive, 0600) != 0 && errno != EEXIST &&
+ (errno != EACCES || geteuid () == 0))
+ eerrorx ("%s: unable to create fifo `%s': %s",
+ applet, exclusive, strerror (errno));
+
+ path = rc_strcatpaths (RC_SVCDIR, "exclusive", applet, (char *) NULL);
+ i = strlen (path) + 16;
+ mtime_test = xmalloc (sizeof (char) * i);
+ snprintf (mtime_test, i, "%s.%d", path, getpid ());
+ free (path);
+
+ if (exists (mtime_test) && unlink (mtime_test) != 0) {
+ eerror ("%s: unlink `%s': %s",
+ applet, mtime_test, strerror (errno));
+ free (mtime_test);
+ mtime_test = NULL;
+ return;
+ }
+
+ if (symlink (service, mtime_test) != 0) {
+ eerror ("%s: symlink `%s' to `%s': %s",
+ applet, service, mtime_test, strerror (errno));
+ free (mtime_test);
+ mtime_test = NULL;
+ }
+}
+
+static void unlink_mtime_test ()
+{
+ if (unlink (mtime_test) != 0)
+ eerror ("%s: unlink `%s': %s", applet, mtime_test, strerror (errno));
+ free (mtime_test);
+ mtime_test = NULL;
+}
+
+static void get_started_services ()
+{
+ rc_strlist_free (tmplist);
+ tmplist = rc_services_in_state (RC_SERVICE_INACTIVE);
+ rc_strlist_free (restart_services);
+ restart_services = rc_services_in_state (RC_SERVICE_STARTED);
+ rc_strlist_join (&restart_services, tmplist);
+ rc_strlist_free (tmplist);
+ tmplist = NULL;
+}
+
+static void svc_start (bool deps)
+{
+ bool started;
+ bool background = false;
+ char *svc;
+ char *svc2;
+ int i;
+ int j;
+ int depoptions = RC_DEP_TRACE;
+ const char *const svcl[] = { applet, NULL };
+ rc_service_state_t state;
+
+ state = rc_service_state (service);
+
+ if (rc_yesno (getenv ("IN_HOTPLUG")) || in_background) {
+ if (! state & RC_SERVICE_INACTIVE &&
+ ! state & RC_SERVICE_STOPPED)
+ exit (EXIT_FAILURE);
+ background = true;
+ }
+
+ if (state & RC_SERVICE_STARTED) {
+ ewarn ("WARNING: %s has already been started", applet);
+ return;
+ } else if (state & RC_SERVICE_STARTING)
+ ewarnx ("WARNING: %s is already starting", applet);
+ else if (state & RC_SERVICE_STOPPING)
+ ewarnx ("WARNING: %s is stopping", applet);
+ else if (state & RC_SERVICE_INACTIVE && ! background)
+ ewarnx ("WARNING: %s has already started, but is inactive", applet);
+
+ if (! rc_service_mark (service, RC_SERVICE_STARTING))
+ eerrorx ("ERROR: %s has been started by something else", applet);
+
+ make_exclusive (service);
+
+ hook_out = RC_HOOK_SERVICE_START_OUT;
+ rc_plugin_run (RC_HOOK_SERVICE_START_IN, applet);
+
+ if (rc_conf_yesno ("rc_depend_strict"))
+ depoptions |= RC_DEP_STRICT;
+
+ if (rc_runlevel_starting ())
+ depoptions |= RC_DEP_START;
+
+ if (deps) {
+ if (! deptree && ((deptree = _rc_deptree_load (NULL)) == NULL))
+ eerrorx ("failed to load deptree");
+
+ rc_strlist_free (services);
+ services = rc_deptree_depends (deptree, types_b, svcl, softlevel, 0);
+ if (services) {
+ eerrorn ("ERROR: `%s' needs ", applet);
+ STRLIST_FOREACH (services, svc, i) {
+ if (i > 0)
+ fprintf (stderr, ", ");
+ fprintf (stderr, "%s", svc);
+ }
+ exit (EXIT_FAILURE);
+ }
+ rc_strlist_free (services);
+ services = NULL;
+
+ rc_strlist_free (need_services);
+ need_services = rc_deptree_depends (deptree, types_n, svcl,
+ softlevel, depoptions);
+
+ rc_strlist_free (use_services);
+ use_services = rc_deptree_depends (deptree, types_nu, svcl,
+ softlevel, depoptions);
+
+ if (! rc_runlevel_starting ()) {
+ STRLIST_FOREACH (use_services, svc, i)
+ if (rc_service_state (svc) & RC_SERVICE_STOPPED) {
+ pid_t pid = rc_service_start (svc);
+ if (! rc_conf_yesno ("rc_parallel"))
+ rc_waitpid (pid);
+ }
+ }
+
+ /* Now wait for them to start */
+ services = rc_deptree_depends (deptree, types_nua, svcl,
+ softlevel, depoptions);
+
+ /* We use tmplist to hold our scheduled by list */
+ rc_strlist_free (tmplist);
+ tmplist = NULL;
+
+ STRLIST_FOREACH (services, svc, i) {
+ rc_service_state_t svcs = rc_service_state (svc);
+ if (svcs & RC_SERVICE_STARTED)
+ continue;
+
+ /* Don't wait for services which went inactive but are now in
+ * starting state which we are after */
+ if (svcs & RC_SERVICE_STARTING &&
+ svcs & RC_SERVICE_WASINACTIVE) {
+ bool use = false;
+ STRLIST_FOREACH (use_services, svc2, j)
+ if (strcmp (svc, svc2) == 0) {
+ use = true;
+ break;
+ }
+ if (! use)
+ continue;
+ }
+
+ if (! svc_wait (deptree, svc))
+ eerror ("%s: timed out waiting for %s", applet, svc);
+ if ((svcs = rc_service_state (svc)) & RC_SERVICE_STARTED)
+ continue;
+
+ STRLIST_FOREACH (need_services, svc2, j)
+ if (strcmp (svc, svc2) == 0) {
+ if (svcs & RC_SERVICE_INACTIVE ||
+ svcs & RC_SERVICE_WASINACTIVE)
+ rc_strlist_add (&tmplist, svc);
+ else
+ eerrorx ("ERROR: cannot start %s as %s would not start",
+ applet, svc);
+ }
+ }
+
+ if (tmplist) {
+ int n = 0;
+ int len = 0;
+ char *p;
+
+ /* Set the state now, then unlink our exclusive so that
+ our scheduled list is preserved */
+ rc_service_mark (service, RC_SERVICE_STOPPED);
+ unlink_mtime_test ();
+
+ STRLIST_FOREACH (tmplist, svc, i) {
+ rc_service_schedule_start (svc, service);
+ rc_strlist_free (providelist);
+ providelist = rc_deptree_depend (deptree, "iprovide", svc);
+ STRLIST_FOREACH (providelist, svc2, j)
+ rc_service_schedule_start (svc2, service);
+
+ len += strlen (svc) + 2;
+ n++;
+ }
+
+ len += 5;
+ tmp = xmalloc (sizeof (char) * len);
+ p = tmp;
+ STRLIST_FOREACH (tmplist, svc, i) {
+ if (i > 1) {
+ if (i == n)
+ p += snprintf (p, len, " or ");
+ else
+ p += snprintf (p, len, ", ");
+ }
+ p += snprintf (p, len, "%s", svc);
+ }
+ ewarnx ("WARNING: %s is scheduled to start when %s has started",
+ applet, tmp);
+ }
+
+ rc_strlist_free (services);
+ services = NULL;
+ }
+
+ if (ibsave)
+ setenv ("IN_BACKGROUND", ibsave, 1);
+ hook_out = RC_HOOK_SERVICE_START_DONE;
+ rc_plugin_run (RC_HOOK_SERVICE_START_NOW, applet);
+ started = svc_exec ("start", NULL);
+ if (ibsave)
+ unsetenv ("IN_BACKGROUND");
+
+ if (in_control ()) {
+ if (! started)
+ eerrorx ("ERROR: %s failed to start", applet);
+ } else {
+ if (rc_service_state (service) & RC_SERVICE_INACTIVE)
+ ewarnx ("WARNING: %s has started, but is inactive", applet);
+ else
+ ewarnx ("WARNING: %s not under our control, aborting", applet);
+ }
+
+ rc_service_mark (service, RC_SERVICE_STARTED);
+ unlink_mtime_test ();
+ hook_out = RC_HOOK_SERVICE_START_OUT;
+ rc_plugin_run (RC_HOOK_SERVICE_START_DONE, applet);
+
+ if (exclusive)
+ unlink (exclusive);
+
+ /* Now start any scheduled services */
+ rc_strlist_free (services);
+ services = rc_services_scheduled (service);
+ STRLIST_FOREACH (services, svc, i)
+ if (rc_service_state (svc) & RC_SERVICE_STOPPED)
+ rc_service_start (svc);
+ rc_strlist_free (services);
+ services = NULL;
+
+ /* Do the same for any services we provide */
+ rc_strlist_free (tmplist);
+ tmplist = rc_deptree_depend (deptree, "iprovide", applet);
+
+ STRLIST_FOREACH (tmplist, svc2, j) {
+ rc_strlist_free (services);
+ services = rc_services_scheduled (svc2);
+ STRLIST_FOREACH (services, svc, i)
+ if (rc_service_state (svc) & RC_SERVICE_STOPPED)
+ rc_service_start (svc);
+ }
+
+ hook_out = 0;
+ rc_plugin_run (RC_HOOK_SERVICE_START_OUT, applet);
+}
+
+static void svc_stop (bool deps)
+{
+ bool stopped;
+ const char *const svcl[] = { applet, NULL };
+
+ rc_service_state_t state = rc_service_state (service);
+
+ 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 (state & RC_SERVICE_STOPPED) {
+ ewarn ("WARNING: %s is already stopped", applet);
+ return;
+ } else if (state & RC_SERVICE_STOPPING)
+ ewarnx ("WARNING: %s is already stopping", applet);
+
+ if (! rc_service_mark (service, RC_SERVICE_STOPPING))
+ eerrorx ("ERROR: %s has been stopped by something else", applet);
+
+ make_exclusive (service);
+
+ hook_out = RC_HOOK_SERVICE_STOP_OUT;
+ rc_plugin_run (RC_HOOK_SERVICE_STOP_IN, applet);
+
+ if (! rc_runlevel_stopping () &&
+ rc_service_in_runlevel (service, RC_LEVEL_BOOT))
+ ewarn ("WARNING: you are stopping a boot service");
+
+ if (deps && ! (state & RC_SERVICE_WASINACTIVE)) {
+ int depoptions = RC_DEP_TRACE;
+ char *svc;
+ int i;
+
+ if (rc_conf_yesno ("rc_depend_strict"))
+ depoptions |= RC_DEP_STRICT;
+
+ if (rc_runlevel_stopping ())
+ depoptions |= RC_DEP_STOP;
+
+ if (! deptree && ((deptree = _rc_deptree_load (NULL)) == NULL))
+ eerrorx ("failed to load deptree");
+
+ rc_strlist_free (tmplist);
+ tmplist = NULL;
+ rc_strlist_free (services);
+ services = rc_deptree_depends (deptree, types_m, svcl,
+ softlevel, depoptions);
+ rc_strlist_reverse (services);
+ STRLIST_FOREACH (services, svc, i) {
+ rc_service_state_t svcs = rc_service_state (svc);
+ if (svcs & RC_SERVICE_STARTED ||
+ svcs & RC_SERVICE_INACTIVE)
+ {
+ svc_wait (deptree, svc);
+ svcs = rc_service_state (svc);
+ if (svcs & RC_SERVICE_STARTED ||
+ svcs & RC_SERVICE_INACTIVE)
+ {
+ pid_t pid = rc_service_stop (svc);
+ if (! rc_conf_yesno ("rc_parallel"))
+ rc_waitpid (pid);
+ rc_strlist_add (&tmplist, svc);
+ }
+ }
+ }
+ rc_strlist_free (services);
+ services = NULL;
+
+ STRLIST_FOREACH (tmplist, svc, i) {
+ if (rc_service_state (svc) & RC_SERVICE_STOPPED)
+ continue;
+
+ /* We used to loop 3 times here - maybe re-do this if needed */
+ svc_wait (deptree, svc);
+ if (! (rc_service_state (svc) & RC_SERVICE_STOPPED)) {
+ if (rc_runlevel_stopping ()) {
+ /* If shutting down, we should stop even if a dependant failed */
+ if (softlevel &&
+ (strcmp (softlevel, RC_LEVEL_SHUTDOWN) == 0 ||
+ strcmp (softlevel, RC_LEVEL_REBOOT) == 0 ||
+ strcmp (softlevel, RC_LEVEL_SINGLE) == 0))
+ continue;
+ rc_service_mark (service, RC_SERVICE_FAILED);
+ }
+
+ eerrorx ("ERROR: cannot stop %s as %s is still up",
+ applet, svc);
+ }
+ }
+ rc_strlist_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, svcl,
+ softlevel, depoptions);
+ STRLIST_FOREACH (services, svc, i) {
+ if (rc_service_state (svc) & RC_SERVICE_STOPPED)
+ continue;
+ svc_wait (deptree, svc);
+ }
+
+ rc_strlist_free (services);
+ services = NULL;
+ }
+
+ if (ibsave)
+ setenv ("IN_BACKGROUND", ibsave, 1);
+ hook_out = RC_HOOK_SERVICE_STOP_DONE;
+ rc_plugin_run (RC_HOOK_SERVICE_STOP_NOW, applet);
+ stopped = svc_exec ("stop", NULL);
+ if (ibsave)
+ unsetenv ("IN_BACKGROUND");
+
+ if (! in_control ())
+ ewarnx ("WARNING: %s not under our control, aborting", applet);
+
+ if (! stopped)
+ eerrorx ("ERROR: %s failed to stop", applet);
+
+ if (in_background)
+ rc_service_mark (service, RC_SERVICE_INACTIVE);
+ else
+ rc_service_mark (service, RC_SERVICE_STOPPED);
+
+ unlink_mtime_test ();
+ hook_out = RC_HOOK_SERVICE_STOP_OUT;
+ rc_plugin_run (RC_HOOK_SERVICE_STOP_DONE, applet);
+ if (exclusive)
+ unlink (exclusive);
+ hook_out = 0;
+ rc_plugin_run (RC_HOOK_SERVICE_STOP_OUT, applet);
+}
+
+static void svc_restart (bool deps)
+{
+ /* 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
+ dns via resolvconf, so you could have openvpn trying to restart dnsmasq
+ which in turn is waiting on net which in turn is waiting on dnsmasq.
+ The work around is for resolvconf to restart it's services with --nodeps
+ which means just that. The downside is that there is a small window when
+ our status is invalid.
+ One workaround would be to introduce a new status, or status locking. */
+ if (! deps) {
+ rc_service_state_t state = rc_service_state (service);
+ if (state & RC_SERVICE_STARTED || state & RC_SERVICE_INACTIVE)
+ svc_exec ("stop", "start");
+ else
+ svc_exec ("start", NULL);
+ return;
+ }
+
+ if (! (rc_service_state (service) & RC_SERVICE_STOPPED)) {
+ get_started_services ();
+ svc_stop (deps);
+ }
+
+ svc_start (deps);
+ start_services (restart_services);
+ rc_strlist_free (restart_services);
+ restart_services = NULL;
+}
+
+#include "_usage.h"
+#define getoptstring "dDsv" getoptstring_COMMON
+#define extraopts "stop | start | restart | describe | zap"
+static struct option longopts[] = {
+ { "debug", 0, NULL, 'd'},
+ { "ifstarted", 0, NULL, 's'},
+ { "nodeps", 0, NULL, 'D'},
+ longopts_COMMON
+};
+static const char * const longopts_help[] = {
+ "set xtrace when running the script",
+ "only run commands when started",
+ "ignore dependencies",
+ longopts_help_COMMON
+};
+#include "_usage.c"
+
+int runscript (int argc, char **argv)
+{
+ int i;
+ bool deps = true;
+ bool doneone = false;
+ char pid[16];
+ int retval;
+ int opt;
+ char *svc;
+
+ /* Show help if insufficient args */
+ if (argc < 2 || ! exists (argv[1])) {
+ fprintf (stderr, "runscript is not meant to be to run directly\n");
+ exit (EXIT_FAILURE);
+ }
+
+ applet = basename_c (argv[1]);
+ if (argc < 3)
+ usage (EXIT_FAILURE);
+
+ if (*argv[1] == '/')
+ service = xstrdup (argv[1]);
+ else {
+ char d[PATH_MAX];
+ getcwd (d, sizeof (d));
+ i = strlen (d) + strlen (argv[1]) + 2;
+ service = xmalloc (sizeof (char) * i);
+ snprintf (service, i, "%s/%s", d, argv[1]);
+ }
+
+ atexit (cleanup);
+
+ /* Change dir to / to ensure all init scripts don't use stuff in pwd */
+ chdir ("/");
+
+#ifdef __linux__
+ /* coldplug events can trigger init scripts, but we don't want to run them
+ until after rc sysinit has completed so we punt them to the boot runlevel */
+ if (exists ("/dev/.rcsysinit")) {
+ eerror ("%s: cannot run until sysvinit completes", applet);
+ if (mkdir ("/dev/.rcboot", 0755) != 0 && errno != EEXIST)
+ eerrorx ("%s: mkdir `/dev/.rcboot': %s", applet, strerror (errno));
+ tmp = rc_strcatpaths ("/dev/.rcboot", applet, (char *) NULL);
+ symlink (service, tmp);
+ exit (EXIT_FAILURE);
+ }
+#endif
+
+ if ((softlevel = xstrdup (getenv ("RC_SOFTLEVEL"))) == NULL) {
+ /* Ensure our environment is pure
+ Also, add our configuration to it */
+ env = env_filter ();
+ tmplist = env_config ();
+ rc_strlist_join (&env, tmplist);
+ rc_strlist_free (tmplist);
+ tmplist = NULL;
+
+ if (env) {
+ char *p;
+
+#ifdef __linux__
+ /* clearenv isn't portable, but there's no harm in using it
+ if we have it */
+ clearenv ();
+#else
+ char *var;
+ /* No clearenv present here then.
+ We could manipulate environ directly ourselves, but it seems that
+ some kernels bitch about this according to the environ man pages
+ so we walk though environ and call unsetenv for each value. */
+ while (environ[0]) {
+ tmp = xstrdup (environ[0]);
+ p = tmp;
+ var = strsep (&p, "=");
+ unsetenv (var);
+ free (tmp);
+ }
+ tmp = NULL;
+#endif
+
+ STRLIST_FOREACH (env, p, i)
+ putenv (p);
+ /* We don't free our list as that would be null in environ */
+ }
+
+ softlevel = rc_runlevel_get ();
+ }
+
+ setenv ("EINFO_LOG", service, 1);
+ setenv ("SVCNAME", applet, 1);
+
+ /* Set an env var so that we always know our pid regardless of any
+ subshells the init script may create so that our mark_service_*
+ functions can always instruct us of this change */
+ snprintf (pid, sizeof (pid), "%d", (int) getpid ());
+ setenv ("RC_RUNSCRIPT_PID", pid, 1);
+
+ /* eprefix is kinda klunky, but it works for our purposes */
+ if (rc_conf_yesno ("rc_parallel")) {
+ int l = 0;
+ int ll;
+
+ /* Get the longest service name */
+ services = rc_services_in_runlevel (NULL);
+ STRLIST_FOREACH (services, svc, i) {
+ ll = strlen (svc);
+ if (ll > l)
+ l = ll;
+ }
+
+ /* Make our prefix string */
+ prefix = xmalloc (sizeof (char) * l);
+ ll = strlen (applet);
+ memcpy (prefix, applet, ll);
+ memset (prefix + ll, ' ', l - ll);
+ memset (prefix + l, 0, 1);
+ eprefix (prefix);
+ }
+
+#ifdef __linux__
+ /* Ok, we are ready to go, so setup selinux if applicable */
+ setup_selinux (argc, argv);
+#endif
+
+ /* Punt the first arg as it's our service name */
+ argc--;
+ argv++;
+
+ /* Right then, parse any options there may be */
+ while ((opt = getopt_long (argc, argv, getoptstring,
+ longopts, (int *) 0)) != -1)
+ switch (opt) {
+ case 'd':
+ setenv ("RC_DEBUG", "yes", 1);
+ break;
+ case 's':
+ if (! (rc_service_state (service) & RC_SERVICE_STARTED))
+ exit (EXIT_FAILURE);
+ break;
+ case 'D':
+ deps = false;
+ break;
+ case_RC_COMMON_GETOPT
+ }
+
+ /* Save the IN_BACKGROUND env flag so it's ONLY passed to the service
+ that is being called and not any dependents */
+ if (getenv ("IN_BACKGROUND")) {
+ ibsave = xstrdup (getenv ("IN_BACKGROUND"));
+ in_background = rc_yesno (ibsave);
+ unsetenv ("IN_BACKGROUND");
+ }
+
+ if (rc_yesno (getenv ("IN_HOTPLUG"))) {
+ if (! rc_conf_yesno ("rc_hotplug") || ! rc_service_plugable (applet))
+ eerrorx ("%s: not allowed to be hotplugged", applet);
+ }
+
+ /* Setup a signal handler */
+ signal (SIGHUP, handle_signal);
+ signal (SIGINT, handle_signal);
+ signal (SIGQUIT, handle_signal);
+ signal (SIGTERM, handle_signal);
+ signal (SIGCHLD, handle_signal);
+
+ /* Load our plugins */
+ rc_plugin_load ();
+
+ /* Now run each option */
+ retval = EXIT_SUCCESS;
+ while (optind < argc) {
+ optarg = argv[optind++];
+
+ /* Abort on a sighup here */
+ if (sighup)
+ exit (EXIT_FAILURE);
+
+ if (strcmp (optarg, "status") != 0 &&
+ strcmp (optarg, "help") != 0) {
+ /* Only root should be able to run us */
+ }
+
+ /* Export the command we're running.
+ This is important as we stamp on the restart function now but
+ some start/stop routines still need to behave differently if
+ restarting. */
+ unsetenv ("RC_CMD");
+ setenv ("RC_CMD", optarg, 1);
+
+ doneone = true;
+
+ if (strcmp (optarg, "describe") == 0 ||
+ strcmp (optarg, "help") == 0)
+ {
+ char *save = prefix;
+
+ eprefix (NULL);
+ prefix = NULL;
+ svc_exec (optarg, NULL);
+ eprefix (save);
+ } else if (strcmp (optarg, "ineed") == 0 ||
+ strcmp (optarg, "iuse") == 0 ||
+ strcmp (optarg, "needsme") == 0 ||
+ strcmp (optarg, "usesme") == 0 ||
+ strcmp (optarg, "iafter") == 0 ||
+ strcmp (optarg, "ibefore") == 0 ||
+ strcmp (optarg, "iprovide") == 0) {
+ int depoptions = RC_DEP_TRACE;
+ const char *t[] = { optarg, NULL };
+ const char *s[] = { applet, NULL };
+
+ if (rc_conf_yesno ("rc_depend_strict"))
+ depoptions |= RC_DEP_STRICT;
+
+ if (! deptree && ((deptree = _rc_deptree_load (NULL)) == NULL))
+ eerrorx ("failed to load deptree");
+
+ rc_strlist_free (services);
+ services = rc_deptree_depends (deptree, t, s, softlevel, depoptions);
+ STRLIST_FOREACH (services, svc, i)
+ printf ("%s%s", i == 1 ? "" : " ", svc);
+ if (services)
+ printf ("\n");
+ } else if (strcmp (optarg, "status") == 0) {
+ rc_service_state_t r = svc_status (service);
+ retval = (int) r;
+ if (retval & RC_SERVICE_STARTED)
+ retval = 0;
+ } else {
+ if (geteuid () != 0)
+ eerrorx ("%s: root access required", applet);
+
+ if (strcmp (optarg, "conditionalrestart") == 0 ||
+ strcmp (optarg, "condrestart") == 0)
+ {
+ if (rc_service_state (service) & RC_SERVICE_STARTED)
+ svc_restart (deps);
+ } else if (strcmp (optarg, "restart") == 0) {
+ svc_restart (deps);
+ } else if (strcmp (optarg, "start") == 0) {
+ svc_start (deps);
+ } else if (strcmp (optarg, "stop") == 0) {
+ if (deps && in_background)
+ get_started_services ();
+
+ svc_stop (deps);
+
+ if (deps) {
+ if (! in_background &&
+ ! rc_runlevel_stopping () &&
+ rc_service_state (service) & RC_SERVICE_STOPPED)
+ uncoldplug ();
+
+ if (in_background &&
+ rc_service_state (service) & RC_SERVICE_INACTIVE)
+ {
+ int j;
+ STRLIST_FOREACH (restart_services, svc, j)
+ if (rc_service_state (svc) & RC_SERVICE_STOPPED)
+ rc_service_schedule_start (service, svc);
+ }
+ }
+ } else if (strcmp (optarg, "zap") == 0) {
+ einfo ("Manually resetting %s to stopped state", applet);
+ rc_service_mark (applet, RC_SERVICE_STOPPED);
+ uncoldplug ();
+ } else
+ svc_exec (optarg, NULL);
+
+ /* We should ensure this list is empty after an action is done */
+ rc_strlist_free (restart_services);
+ restart_services = NULL;
+ }
+
+ if (! doneone)
+ usage (EXIT_FAILURE);
+ }
+
+ return (retval);
+}
diff --git a/src/rc/start-stop-daemon.c b/src/rc/start-stop-daemon.c
new file mode 100644
index 0000000..bf03dbe
--- /dev/null
+++ b/src/rc/start-stop-daemon.c
@@ -0,0 +1,1070 @@
+/*
+ start-stop-daemon
+ Starts, stops, tests and signals daemons
+
+ This is essentially a ground up re-write of Debians
+ start-stop-daemon for cleaner code and to integrate into our RC
+ system so we can monitor daemons a little.
+ */
+
+/*
+ * Copyright 2007 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.
+ */
+
+/* nano seconds */
+#define POLL_INTERVAL 20000000
+#define WAIT_PIDFILE 500000000
+#define START_WAIT 100000000
+#define ONE_SECOND 1000000000
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/termios.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <grp.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifdef HAVE_PAM
+#include <security/pam_appl.h>
+
+/* We are not supporting authentication conversations */
+static struct pam_conv conv = { NULL, NULL};
+#endif
+
+#include "builtins.h"
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "strlist.h"
+
+typedef struct schedulelist
+{
+ enum
+ {
+ schedule_timeout,
+ schedule_signal,
+ schedule_goto,
+ schedule_forever
+ } type;
+ int value;
+ struct schedulelist *gotolist;
+ struct schedulelist *next;
+} schedulelist_t;
+static schedulelist_t *schedule;
+
+static const char *applet;
+static char *changeuser;
+static char **newenv;
+
+extern char **environ;
+
+static void free_schedulelist (schedulelist_t **list)
+{
+ schedulelist_t *here;
+ schedulelist_t *next;
+
+ for (here = *list; here; here = next) {
+ next = here->next;
+ free (here);
+ }
+
+ *list = NULL;
+}
+
+static void cleanup (void)
+{
+ if (changeuser)
+ free (changeuser);
+
+ if (schedule)
+ free_schedulelist (&schedule);
+
+ if (newenv)
+ rc_strlist_free (newenv);
+}
+
+static int parse_signal (const char *sig)
+{
+ typedef struct signalpair
+ {
+ const char *name;
+ int signal;
+ } signalpair_t;
+
+ static const signalpair_t signallist[] = {
+ { "ABRT", SIGABRT },
+ { "ALRM", SIGALRM },
+ { "FPE", SIGFPE },
+ { "HUP", SIGHUP },
+ { "ILL", SIGILL },
+ { "INT", SIGINT },
+ { "KILL", SIGKILL },
+ { "PIPE", SIGPIPE },
+ { "QUIT", SIGQUIT },
+ { "SEGV", SIGSEGV },
+ { "TERM", SIGTERM },
+ { "USR1", SIGUSR1 },
+ { "USR2", SIGUSR2 },
+ { "CHLD", SIGCHLD },
+ { "CONT", SIGCONT },
+ { "STOP", SIGSTOP },
+ { "TSTP", SIGTSTP },
+ { "TTIN", SIGTTIN },
+ { "TTOU", SIGTTOU }
+ };
+
+ unsigned int i = 0;
+ char *s;
+
+ if (! sig || strlen (sig) == 0)
+ return (-1);
+
+ if (sscanf (sig, "%u", &i) == 1) {
+ if (i > 0 && i < sizeof (signallist) / sizeof (signallist[0]))
+ return (i);
+ eerrorx ("%s: `%s' is not a valid signal", applet, sig);
+ }
+
+ if (strncmp (sig, "SIG", 3) == 0)
+ s = (char *) sig + 3;
+ else
+ s = NULL;
+
+ for (i = 0; i < sizeof (signallist) / sizeof (signallist[0]); i++)
+ if (strcmp (sig, signallist[i].name) == 0 ||
+ (s && strcmp (s, signallist[i].name) == 0))
+ return (signallist[i].signal);
+
+ eerrorx ("%s: `%s' is not a valid signal", applet, sig);
+}
+
+static void parse_schedule_item (schedulelist_t *item, const char *string)
+{
+ const char *after_hyph;
+ int sig;
+
+ if (strcmp (string,"forever") == 0)
+ item->type = schedule_forever;
+ else if (isdigit (string[0])) {
+ item->type = schedule_timeout;
+ errno = 0;
+ if (sscanf (string, "%d", &item->value) != 1)
+ eerrorx ("%s: invalid timeout value in schedule `%s'", applet,
+ string);
+ } else if ((after_hyph = string + (string[0] == '-')) &&
+ ((sig = parse_signal (after_hyph)) != -1))
+ {
+ item->type = schedule_signal;
+ item->value = (int) sig;
+ }
+ else
+ eerrorx ("%s: invalid schedule item `%s'", applet, string);
+}
+
+static void parse_schedule (const char *string, int default_signal)
+{
+ char buffer[20];
+ const char *slash;
+ int count = 0;
+ schedulelist_t *repeatat = NULL;
+ ptrdiff_t len;
+ schedulelist_t *next;
+
+ if (string)
+ for (slash = string; *slash; slash++)
+ if (*slash == '/')
+ count++;
+
+ if (schedule)
+ free_schedulelist (&schedule);
+
+ schedule = xmalloc (sizeof (schedulelist_t));
+ schedule->gotolist = NULL;
+
+ if (count == 0) {
+ schedule->type = schedule_signal;
+ schedule->value = default_signal;
+ schedule->next = xmalloc (sizeof (schedulelist_t));
+ next = schedule->next;
+ next->type = schedule_timeout;
+ next->gotolist = NULL;
+ if (string) {
+ if (sscanf (string, "%d", &next->value) != 1)
+ eerrorx ("%s: invalid timeout value in schedule", applet);
+ }
+ else
+ next->value = 5;
+ next->next = NULL;
+
+ return;
+ }
+
+ next = schedule;
+ while (string != NULL) {
+ if ((slash = strchr (string, '/')))
+ len = slash - string;
+ else
+ len = strlen (string);
+
+ if (len >= (ptrdiff_t) sizeof (buffer))
+ eerrorx ("%s: invalid schedule item, far too long", applet);
+
+ memcpy (buffer, string, len);
+ buffer[len] = 0;
+ string = slash ? slash + 1 : NULL;
+
+ parse_schedule_item (next, buffer);
+ if (next->type == schedule_forever) {
+ if (repeatat)
+ eerrorx ("%s: invalid schedule, `forever' appears more than once",
+ applet);
+
+ repeatat = next;
+ continue;
+ }
+
+ if (string) {
+ next->next = xmalloc (sizeof (schedulelist_t));
+ next = next->next;
+ next->gotolist = NULL;
+ }
+ }
+
+ if (repeatat) {
+ next->next = xmalloc (sizeof (schedulelist_t));
+ next = next->next;
+ next->type = schedule_goto;
+ next->value = 0;
+ next->gotolist = repeatat;
+ }
+
+ next->next = NULL;
+ return;
+}
+
+static pid_t get_pid (const char *pidfile, bool quiet)
+{
+ FILE *fp;
+ pid_t pid;
+
+ if (! pidfile)
+ return (-1);
+
+ if ((fp = fopen (pidfile, "r")) == NULL) {
+ if (! quiet)
+ eerror ("%s: fopen `%s': %s", applet, pidfile, strerror (errno));
+ return (-1);
+ }
+
+ if (fscanf (fp, "%d", &pid) != 1) {
+ if (! quiet)
+ eerror ("%s: no pid found in `%s'", applet, pidfile);
+ fclose (fp);
+ return (-1);
+ }
+ fclose (fp);
+
+ return (pid);
+}
+
+/* return number of processed killed, -1 on error */
+static int do_stop (const char *exec, const char *cmd,
+ const char *pidfile, uid_t uid,int sig,
+ bool quiet, bool verbose, bool test)
+{
+ pid_t *pids;
+ bool killed;
+ int nkilled = 0;
+ pid_t pid = 0;
+ int i;
+
+ if (pidfile) {
+ if ((pid = get_pid (pidfile, quiet)) == -1)
+ return (quiet ? 0 : -1);
+ pids = rc_find_pids (NULL, NULL, 0, pid);
+ } else
+ pids = rc_find_pids (exec, cmd, uid, pid);
+
+ if (! pids)
+ return (0);
+
+ for (i = 0; pids[i]; i++) {
+ if (test) {
+ if (! quiet)
+ einfo ("Would send signal %d to PID %d", sig, pids[i]);
+ nkilled++;
+ continue;
+ }
+
+ if (verbose)
+ ebegin ("Sending signal %d to PID %d", sig, pids[i]);
+ errno = 0;
+ killed = (kill (pids[i], sig) == 0 || errno == ESRCH ? true : false);
+ if (verbose)
+ eend (killed ? 0 : 1, "%s: failed to send signal %d to PID %d: %s",
+ applet, sig, pids[i], strerror (errno));
+ if (! killed) {
+ nkilled = -1;
+ } else {
+ if (nkilled != -1)
+ nkilled++;
+ }
+ }
+
+ free (pids);
+ return (nkilled);
+}
+
+static int run_stop_schedule (const char *exec, const char *cmd,
+ const char *pidfile, uid_t uid,
+ bool quiet, bool verbose, bool test)
+{
+ schedulelist_t *item = schedule;
+ int nkilled = 0;
+ int tkilled = 0;
+ int nrunning = 0;
+ long nloops;
+ struct timespec ts;
+
+ if (verbose) {
+ if (pidfile)
+ einfo ("Will stop PID in pidfile `%s'", pidfile);
+ if (uid)
+ einfo ("Will stop processes owned by UID %d", uid);
+ if (exec)
+ einfo ("Will stop processes of `%s'", exec);
+ if (cmd)
+ einfo ("Will stop processes called `%s'", cmd);
+ }
+
+ while (item) {
+ switch (item->type) {
+ case schedule_goto:
+ item = item->gotolist;
+ continue;
+
+ case schedule_signal:
+ nrunning = 0;
+ nkilled = do_stop (exec, cmd, pidfile, uid, item->value,
+ quiet, verbose, test);
+ if (nkilled == 0) {
+ if (tkilled == 0) {
+ if (! quiet)
+ eerror ("%s: no matching processes found", applet);
+ }
+ return (tkilled);
+ }
+ else if (nkilled == -1)
+ return (0);
+
+ tkilled += nkilled;
+ break;
+ case schedule_timeout:
+ if (item->value < 1) {
+ item = NULL;
+ break;
+ }
+
+ nloops = (ONE_SECOND / POLL_INTERVAL) * item->value;
+ ts.tv_sec = 0;
+ ts.tv_nsec = POLL_INTERVAL;
+
+ while (nloops) {
+ if ((nrunning = do_stop (exec, cmd, pidfile,
+ uid, 0, true, false, true)) == 0)
+ return (true);
+
+ if (nanosleep (&ts, NULL) == -1) {
+ if (errno == EINTR)
+ eerror ("%s: caught an interrupt", applet);
+ else {
+ eerror ("%s: nanosleep: %s", applet, strerror (errno));
+ return (0);
+ }
+ }
+ nloops --;
+ }
+ break;
+
+ default:
+ eerror ("%s: invalid schedule item `%d'", applet, item->type);
+ return (0);
+ }
+
+ if (item)
+ item = item->next;
+ }
+
+ if (test || (tkilled > 0 && nrunning == 0))
+ return (nkilled);
+
+ if (! quiet) {
+ if (nrunning == 1)
+ eerror ("%s: %d process refused to stop", applet, nrunning);
+ else
+ eerror ("%s: %d process(es) refused to stop", applet, nrunning);
+ }
+
+ return (-nrunning);
+}
+
+static void handle_signal (int sig)
+{
+ int pid;
+ int status;
+ int serrno = errno;
+ char signame[10] = { '\0' };
+
+ switch (sig) {
+ case SIGINT:
+ if (! signame[0])
+ snprintf (signame, sizeof (signame), "SIGINT");
+ case SIGTERM:
+ if (! signame[0])
+ snprintf (signame, sizeof (signame), "SIGTERM");
+ case SIGQUIT:
+ if (! signame[0])
+ snprintf (signame, sizeof (signame), "SIGQUIT");
+ eerrorx ("%s: caught %s, aborting", applet, signame);
+
+ case SIGCHLD:
+ while (1) {
+ if ((pid = waitpid (-1, &status, WNOHANG)) < 0) {
+ if (errno != ECHILD)
+ eerror ("%s: waitpid: %s", applet, strerror (errno));
+ break;
+ }
+ }
+ break;
+
+ default:
+ eerror ("%s: caught unknown signal %d", applet, sig);
+ }
+
+ /* Restore errno */
+ errno = serrno;
+}
+
+
+#include "_usage.h"
+#define getoptstring "KN:R:Sbc:d:g:mn:op:s:tu:r:x:1:2:" getoptstring_COMMON
+static struct option longopts[] = {
+ { "stop", 0, NULL, 'K'},
+ { "nicelevel", 1, NULL, 'N'},
+ { "retry", 1, NULL, 'R'},
+ { "start", 0, NULL, 'S'},
+ { "startas", 1, NULL, 'a'},
+ { "background", 0, NULL, 'b'},
+ { "chuid", 1, NULL, 'c'},
+ { "chdir", 1, NULL, 'd'},
+ { "group", 1, NULL, 'g'},
+ { "make-pidfile", 0, NULL, 'm'},
+ { "name", 1, NULL, 'n'},
+ { "oknodo", 0, NULL, 'o'},
+ { "pidfile", 1, NULL, 'p'},
+ { "signal", 1, NULL, 's'},
+ { "test", 0, NULL, 't'},
+ { "user", 1, NULL, 'u'},
+ { "chroot", 1, NULL, 'r'},
+ { "exec", 1, NULL, 'x'},
+ { "stdout", 1, NULL, '1'},
+ { "stderr", 1, NULL, '2'},
+ longopts_COMMON
+};
+static const char * const longopts_help[] = {
+ "Stop daemon",
+ "Set a nicelevel when starting",
+ "Retry schedule to use when stopping",
+ "Start daemon",
+ "deprecated, use --exec",
+ "Force daemon to background",
+ "deprecated, use --user",
+ "Change the PWD",
+ "Change the process group",
+ "Create a pidfile",
+ "Match process name",
+ "deprecated",
+ "Match pid found in this file",
+ "Send a different signal",
+ "Test actions, don't do them",
+ "Change the process user",
+ "Chroot to this directory",
+ "Binary to start/stop",
+ "Redirect stdout to file",
+ "Redirect stderr to file",
+ longopts_help_COMMON
+};
+#include "_usage.c"
+
+int start_stop_daemon (int argc, char **argv)
+{
+ int devnull_fd = -1;
+#ifdef TIOCNOTTY
+ int tty_fd = -1;
+#endif
+
+#ifdef HAVE_PAM
+ pam_handle_t *pamh = NULL;
+ int pamr;
+#endif
+
+ int opt;
+ bool start = false;
+ bool stop = false;
+ bool oknodo = false;
+ bool test = false;
+ bool quiet;
+ bool verbose = false;
+ char *exec = NULL;
+ char *cmd = NULL;
+ char *pidfile = NULL;
+ int sig = SIGTERM;
+ int nicelevel = 0;
+ bool background = false;
+ bool makepidfile = false;
+ uid_t uid = 0;
+ gid_t gid = 0;
+ char *ch_root = NULL;
+ char *ch_dir = NULL;
+ int tid = 0;
+ char *redirect_stderr = NULL;
+ char *redirect_stdout = NULL;
+ int stdout_fd;
+ int stderr_fd;
+ pid_t pid;
+ int i;
+ char *svcname = getenv ("SVCNAME");
+ char *env;
+
+ applet = basename_c (argv[0]);
+ atexit (cleanup);
+
+ signal (SIGINT, handle_signal);
+ signal (SIGQUIT, handle_signal);
+ signal (SIGTERM, handle_signal);
+
+ if ((env = getenv ("SSD_NICELEVEL")))
+ if (sscanf (env, "%d", &nicelevel) != 1)
+ eerror ("%s: invalid nice level `%s' (SSD_NICELEVEL)", applet, env);
+
+ while ((opt = getopt_long (argc, argv, getoptstring, longopts,
+ (int *) 0)) != -1)
+ switch (opt) {
+ case 'K': /* --stop */
+ stop = true;
+ break;
+
+ case 'N': /* --nice */
+ if (sscanf (optarg, "%d", &nicelevel) != 1)
+ eerrorx ("%s: invalid nice level `%s'", applet, optarg);
+ break;
+
+ case 'R': /* --retry <schedule>|<timeout> */
+ parse_schedule (optarg, sig);
+ break;
+
+ case 'S': /* --start */
+ start = true;
+ break;
+
+ case 'b': /* --background */
+ background = true;
+ break;
+
+ case 'u': /* --user <username>|<uid> */
+ case 'c': /* --chuid <username>|<uid> */
+ {
+ char *p = optarg;
+ char *cu = strsep (&p, ":");
+ struct passwd *pw = NULL;
+
+ changeuser = xstrdup (cu);
+ if (sscanf (cu, "%d", &tid) != 1)
+ pw = getpwnam (cu);
+ else
+ pw = getpwuid (tid);
+
+ if (! pw)
+ eerrorx ("%s: user `%s' not found", applet, cu);
+ uid = pw->pw_uid;
+ if (! gid)
+ gid = pw->pw_gid;
+
+ if (p) {
+ struct group *gr = NULL;
+ char *cg = strsep (&p, ":");
+
+ if (sscanf (cg, "%d", &tid) != 1)
+ gr = getgrnam (cg);
+ else
+ gr = getgrgid (tid);
+
+ if (! gr)
+ eerrorx ("%s: group `%s' not found", applet, cg);
+ gid = gr->gr_gid;
+ }
+ }
+ break;
+
+ case 'd': /* --chdir /new/dir */
+ ch_dir = optarg;
+ break;
+
+ case 'g': /* --group <group>|<gid> */
+ {
+ struct group *gr = getgrnam (optarg);
+
+ if (sscanf (optarg, "%d", &tid) != 1)
+ gr = getgrnam (optarg);
+ else
+ gr = getgrgid (tid);
+
+ if (! gr)
+ eerrorx ("%s: group `%s' not found", applet, optarg);
+ gid = gr->gr_gid;
+ }
+ break;
+
+ case 'm': /* --make-pidfile */
+ makepidfile = true;
+ break;
+
+ case 'n': /* --name <process-name> */
+ cmd = optarg;
+ break;
+
+ case 'o': /* --oknodo */
+ oknodo = true;
+ break;
+
+ case 'p': /* --pidfile <pid-file> */
+ pidfile = optarg;
+ break;
+
+ case 's': /* --signal <signal> */
+ sig = parse_signal (optarg);
+ break;
+
+ case 't': /* --test */
+ test = true;
+ break;
+
+ case 'r': /* --chroot /new/root */
+ ch_root = optarg;
+ break;
+
+ case 'a':
+ case 'x': /* --exec <executable> */
+ exec = optarg;
+ break;
+
+ case '1': /* --stdout /path/to/stdout.lgfile */
+ redirect_stdout = optarg;
+ break;
+
+ case '2': /* --stderr /path/to/stderr.logfile */
+ redirect_stderr = optarg;
+ break;
+
+ case_RC_COMMON_GETOPT
+ }
+
+ quiet = rc_yesno (getenv ("EINFO_QUIET"));
+ verbose = rc_yesno (getenv ("EINFO_VERBOSE"));
+
+ /* Allow start-stop-daemon --signal HUP --exec /usr/sbin/dnsmasq
+ * instead of forcing --stop --oknodo as well */
+ if (! start && ! stop)
+ if (sig != SIGINT &&
+ sig != SIGTERM &&
+ sig != SIGQUIT &&
+ sig != SIGKILL)
+ {
+ oknodo = true;
+ stop = true;
+ }
+
+ if (start == stop)
+ eerrorx ("%s: need one of --start or --stop", applet);
+
+ if (start && ! exec)
+ eerrorx ("%s: --start needs --exec", applet);
+
+ if (stop && ! exec && ! pidfile && ! cmd && ! uid)
+ eerrorx ("%s: --stop needs --exec, --pidfile, --name or --user", applet);
+
+ if (makepidfile && ! pidfile)
+ eerrorx ("%s: --make-pidfile is only relevant with --pidfile", applet);
+
+ if (background && ! start)
+ eerrorx ("%s: --background is only relevant with --start", applet);
+
+ if ((redirect_stdout || redirect_stderr) && ! background)
+ eerrorx ("%s: --stdout and --stderr are only relevant with --background",
+ applet);
+
+ argc -= optind;
+ argv += optind;
+
+ /* Validate that the binary exists if we are starting */
+ if (exec && start) {
+ char *tmp;
+ if (ch_root)
+ tmp = rc_strcatpaths (ch_root, exec, (char *) NULL);
+ else
+ tmp = exec;
+ if (! exists (tmp)) {
+ eerror ("%s: %s does not exist", applet, tmp);
+ if (ch_root)
+ free (tmp);
+ exit (EXIT_FAILURE);
+ }
+ if (ch_root)
+ free (tmp);
+ }
+
+ if (stop) {
+ int result;
+
+ if (! schedule) {
+ if (test || oknodo)
+ parse_schedule ("0", sig);
+ else
+ parse_schedule (NULL, sig);
+ }
+
+ result = run_stop_schedule (exec, cmd, pidfile, uid, quiet, verbose, test);
+ if (test || oknodo)
+ return (result > 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+ if (result < 1)
+ exit (result == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
+
+ if (pidfile && exists (pidfile))
+ unlink (pidfile);
+
+ if (svcname)
+ rc_service_daemon_set (svcname, exec, cmd, pidfile, false);
+
+ exit (EXIT_SUCCESS);
+ }
+
+ if (do_stop (exec, cmd, pidfile, uid, 0, true, false, true) > 0)
+ eerrorx ("%s: %s is already running", applet, exec);
+
+ if (test) {
+ if (quiet)
+ exit (EXIT_SUCCESS);
+
+ einfon ("Would start %s", exec);
+ while (argc-- > 0)
+ printf("%s ", *argv++);
+ printf ("\n");
+ eindent ();
+ if (uid != 0)
+ einfo ("as user id %d", uid);
+ if (gid != 0)
+ einfo ("as group id %d", gid);
+ if (ch_root)
+ einfo ("in root `%s'", ch_root);
+ if (ch_dir)
+ einfo ("in dir `%s'", ch_dir);
+ if (nicelevel != 0)
+ einfo ("with a priority of %d", nicelevel);
+ eoutdent ();
+ exit (EXIT_SUCCESS);
+ }
+
+ /* Ensure this is unset, so if the daemon does /etc/init.d/foo
+ Then we filter the environment accordingly */
+ unsetenv ("RC_SOFTLEVEL");
+
+ if (verbose) {
+ ebegin ("Detaching to start `%s'", exec);
+ eindent ();
+ }
+
+ if (background)
+ signal (SIGCHLD, handle_signal);
+
+ *--argv = exec;
+ if ((pid = fork ()) == -1)
+ eerrorx ("%s: fork: %s", applet, strerror (errno));
+
+ /* Child process - lets go! */
+ if (pid == 0) {
+ pid_t mypid = getpid ();
+
+#ifdef TIOCNOTTY
+ tty_fd = open("/dev/tty", O_RDWR);
+#endif
+
+ devnull_fd = open("/dev/null", O_RDWR);
+
+ if (nicelevel) {
+ if (setpriority (PRIO_PROCESS, mypid, nicelevel) == -1)
+ eerrorx ("%s: setpritory %d: %s", applet, nicelevel,
+ strerror(errno));
+ }
+
+ if (ch_root && chroot (ch_root) < 0)
+ eerrorx ("%s: chroot `%s': %s", applet, ch_root, strerror (errno));
+
+ if (ch_dir && chdir (ch_dir) < 0)
+ eerrorx ("%s: chdir `%s': %s", applet, ch_dir, strerror (errno));
+
+ if (makepidfile && pidfile) {
+ FILE *fp = fopen (pidfile, "w");
+ if (! fp)
+ eerrorx ("%s: fopen `%s': %s", applet, pidfile, strerror
+ (errno));
+ fprintf (fp, "%d\n", mypid);
+ fclose (fp);
+ }
+
+#ifdef HAVE_PAM
+ if (changeuser != NULL)
+ pamr = pam_start ("start-stop-daemon", changeuser, &conv, &pamh);
+ else
+ pamr = pam_start ("start-stop-daemon", "nobody", &conv, &pamh);
+
+ if (pamr == PAM_SUCCESS)
+ pamr = pam_authenticate (pamh, PAM_SILENT);
+ if (pamr == PAM_SUCCESS)
+ pamr = pam_acct_mgmt (pamh, PAM_SILENT);
+ if (pamr == PAM_SUCCESS)
+ pamr = pam_open_session (pamh, PAM_SILENT);
+ if (pamr != PAM_SUCCESS)
+ eerrorx ("%s: pam error: %s", applet, pam_strerror(pamh, pamr));
+#endif
+
+ if (gid && setgid (gid))
+ eerrorx ("%s: unable to set groupid to %d", applet, gid);
+ if (changeuser && initgroups (changeuser, gid))
+ eerrorx ("%s: initgroups (%s, %d)", applet, changeuser, gid);
+ if (uid && setuid (uid))
+ eerrorx ("%s: unable to set userid to %d", applet, uid);
+ else {
+ struct passwd *passwd = getpwuid (uid);
+ if (passwd) {
+ unsetenv ("HOME");
+ if (passwd->pw_dir)
+ setenv ("HOME", passwd->pw_dir, 1);
+ unsetenv ("USER");
+ if (passwd->pw_name)
+ setenv ("USER", passwd->pw_name, 1);
+ }
+ }
+
+ /* Close any fd's to the passwd database */
+ endpwent ();
+
+#ifdef TIOCNOTTY
+ ioctl(tty_fd, TIOCNOTTY, 0);
+ close(tty_fd);
+#endif
+
+ /* Clean the environment of any RC_ variables */
+ STRLIST_FOREACH (environ, env, i) {
+ if (strncmp (env, "RC_", 3) == 0 ||
+ strncmp (env, "SSD_NICELEVEL=", strlen ("SSD_NICELEVEL=")) == 0)
+ continue;
+
+ /* For the path, remove the rcscript bin dir from it */
+ if (strncmp (env, "PATH=", 5) == 0) {
+ char *path = xstrdup (env);
+ char *newpath = NULL;
+ char *p = path;
+ char *token;
+ char *np;
+ int l;
+ int t;
+
+ p += 5;
+ while ((token = strsep (&p, ":"))) {
+ if (strcmp (token, RC_LIBDIR "/bin") == 0 ||
+ strcmp (token, RC_LIBDIR "/sbin") == 0)
+ continue;
+
+ t = strlen (token);
+ if (newpath) {
+ l = strlen (newpath);
+ newpath = xrealloc (newpath, sizeof (char) * (l + t + 2));
+ np = newpath + l;
+ *np++ = ':';
+ memcpy (np, token, sizeof (char) * strlen (token));
+ np += t;
+ *np = '\0';
+ } else {
+ l = strlen ("PATH=") + t + 1;
+ newpath = xmalloc (sizeof (char) * l);
+ snprintf (newpath, l, "PATH=%s", token);
+ }
+ }
+ rc_strlist_add (&newenv, newpath);
+ free (path);
+ free (newpath);
+ } else
+ rc_strlist_add (&newenv, env);
+ }
+
+ umask (022);
+
+ stdout_fd = devnull_fd;
+ stderr_fd = devnull_fd;
+ if (redirect_stdout) {
+ if ((stdout_fd = open (redirect_stdout, O_WRONLY | O_CREAT | O_APPEND,
+ S_IRUSR | S_IWUSR)) == -1)
+ eerrorx ("%s: unable to open the logfile for stdout `%s': %s",
+ applet, redirect_stdout, strerror (errno));
+ }
+ if (redirect_stderr) {
+ if ((stderr_fd = open (redirect_stderr, O_WRONLY | O_CREAT | O_APPEND,
+ S_IRUSR | S_IWUSR)) == -1)
+ eerrorx ("%s: unable to open the logfile for stderr `%s': %s",
+ applet, redirect_stderr, strerror (errno));
+ }
+
+ if (background) {
+ /* Hmmm, some daemons may need stdin? */
+ dup2 (devnull_fd, STDIN_FILENO);
+ dup2 (stdout_fd, STDOUT_FILENO);
+ dup2 (stderr_fd, STDERR_FILENO);
+ }
+
+ for (i = getdtablesize () - 1; i >= 3; --i)
+ close(i);
+
+ setsid ();
+
+ execve (exec, argv, newenv);
+#ifdef HAVE_PAM
+ if (pamr == PAM_SUCCESS)
+ pam_close_session (pamh, PAM_SILENT);
+#endif
+ eerrorx ("%s: failed to exec `%s': %s", applet, exec, strerror (errno));
+ }
+
+ /* Parent process */
+ if (! background) {
+ /* As we're not backgrounding the process, wait for our pid to return */
+ int status = 0;
+ int savepid = pid;
+
+ errno = 0;
+ do {
+ pid = waitpid (savepid, &status, 0);
+ if (pid < 1) {
+ eerror ("waitpid %d: %s", savepid, strerror (errno));
+ return (-1);
+ }
+ } while (! WIFEXITED (status) && ! WIFSIGNALED (status));
+
+ if (! WIFEXITED (status) || WEXITSTATUS (status) != 0) {
+ if (! quiet)
+ eerrorx ("%s: failed to start `%s'", applet, exec);
+ exit (EXIT_FAILURE);
+ }
+
+ pid = savepid;
+ }
+
+ /* Wait a little bit and check that process is still running
+ We do this as some badly written daemons fork and then barf */
+ if (START_WAIT > 0) {
+ struct timespec ts;
+ int nloops = START_WAIT / POLL_INTERVAL;
+ int nloopsp = WAIT_PIDFILE / POLL_INTERVAL;
+ bool alive = false;
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = POLL_INTERVAL;
+
+ while (nloops) {
+ if (nanosleep (&ts, NULL) == -1) {
+ if (errno == EINTR)
+ eerror ("%s: caught an interrupt", applet);
+ else {
+ eerror ("%s: nanosleep: %s", applet, strerror (errno));
+ return (0);
+ }
+ }
+
+ /* We wait for a specific amount of time for a pidfile to be
+ * created. Once everything is in place we then wait some more
+ * to ensure that the daemon really is running and won't abort due
+ * to a config error. */
+ if (! background && pidfile && nloopsp)
+ nloopsp --;
+ else
+ nloops --;
+
+ /* This is knarly.
+ If we backgrounded then we know the exact pid.
+ Otherwise if we have a pidfile then it *may* know the exact pid.
+ Failing that, we'll have to query processes.
+ We sleep first as some programs like ntp like to fork, and write
+ their pidfile a LONG time later. */
+ if (background) {
+ if (kill (pid, 0) == 0)
+ alive = true;
+ } else {
+ if (pidfile) {
+ /* The pidfile may not have been written yet - give it some time */
+ if (get_pid (pidfile, true) == -1) {
+ if (! nloopsp)
+ eerrorx ("%s: did not create a valid pid in `%s'",
+ applet, pidfile);
+ alive = true;
+ } else
+ nloopsp = 0;
+ }
+ if (do_stop (exec, cmd, pidfile, uid, 0, true, false, true) > 0)
+ alive = true;
+ }
+
+ if (! alive)
+ eerrorx ("%s: %s died", applet, exec);
+ }
+ }
+
+ if (svcname)
+ rc_service_daemon_set (svcname, exec, cmd, pidfile, true);
+
+ exit (EXIT_SUCCESS);
+}
diff --git a/src/rc/start-stop-daemon.pam b/src/rc/start-stop-daemon.pam
new file mode 100644
index 0000000..860a3d5
--- /dev/null
+++ b/src/rc/start-stop-daemon.pam
@@ -0,0 +1,6 @@
+#%PAM-1.0
+
+auth sufficient pam_rootok.so
+account required pam_permit.so
+password required pam_deny.so
+session optional pam_limits.so