diff options
Diffstat (limited to 'src/rc')
-rw-r--r-- | src/rc/_usage.c | 55 | ||||
-rw-r--r-- | src/rc/_usage.h | 53 | ||||
-rw-r--r-- | src/rc/builtins.h | 39 | ||||
-rw-r--r-- | src/rc/checkpath.c | 233 | ||||
-rw-r--r-- | src/rc/fstabinfo.c | 235 | ||||
-rw-r--r-- | src/rc/mountinfo.c | 464 | ||||
-rw-r--r-- | src/rc/rc-depend.c | 192 | ||||
-rw-r--r-- | src/rc/rc-logger.c | 258 | ||||
-rw-r--r-- | src/rc/rc-logger.h | 32 | ||||
-rw-r--r-- | src/rc/rc-misc.c | 353 | ||||
-rw-r--r-- | src/rc/rc-plugin.c | 241 | ||||
-rw-r--r-- | src/rc/rc-plugin.h | 55 | ||||
-rw-r--r-- | src/rc/rc-status.c | 197 | ||||
-rw-r--r-- | src/rc/rc-update.c | 272 | ||||
-rw-r--r-- | src/rc/rc.c | 1574 | ||||
-rw-r--r-- | src/rc/rc.map | 58 | ||||
-rw-r--r-- | src/rc/runscript.c | 1311 | ||||
-rw-r--r-- | src/rc/start-stop-daemon.c | 1070 | ||||
-rw-r--r-- | src/rc/start-stop-daemon.pam | 6 |
19 files changed, 6698 insertions, 0 deletions
diff --git a/src/rc/_usage.c b/src/rc/_usage.c new file mode 100644 index 00000000..b079e320 --- /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 00000000..e4a5fa3b --- /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 00000000..43a4d9f9 --- /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 00000000..612a0769 --- /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 00000000..5f8e469a --- /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 00000000..05ce8dd7 --- /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 00000000..c5e9e662 --- /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 00000000..675a4d23 --- /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 00000000..c15e73f8 --- /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 00000000..0d8b8c1f --- /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 00000000..613f049e --- /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 00000000..412a47e7 --- /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 00000000..155192fa --- /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 00000000..4f07503f --- /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 00000000..a33b6dcf --- /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 (®en)) == 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 00000000..e5f8ee34 --- /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 00000000..1385bb02 --- /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 00000000..bf03dbec --- /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 00000000..860a3d52 --- /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 |