diff options
author | Roy Marples <roy@marples.name> | 2008-01-05 19:25:55 +0000 |
---|---|---|
committer | Roy Marples <roy@marples.name> | 2008-01-05 19:25:55 +0000 |
commit | ac21d75300dabe83578e4373fcfd09d67c3a083b (patch) | |
tree | d5f8e2a16920add2277c79ff8a1b7e99ec2976df /src/rc | |
parent | 112fbde453d55c49b7999d2e35496a8758aaa7b5 (diff) |
Add some .mk stubs to impersonate bsk .mk files to make writing our Makefiles easier. libeinfo, librc and rc now have their own seperate directories. More work is needed to tidy this up though.
Diffstat (limited to 'src/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 |