From ac21d75300dabe83578e4373fcfd09d67c3a083b Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Sat, 5 Jan 2008 19:25:55 +0000 Subject: 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. --- src/.mk | 0 src/Makefile. | 4 - src/Makefile.BSD | 2 - src/Makefile.Linux | 2 - src/Makefile.ncurses | 2 - src/Makefile.pam | 2 - src/Makefile.termcap | 4 - src/_usage.c | 55 -- src/_usage.h | 53 -- src/builtins.h | 39 -- src/cc.mk | 25 + src/checkpath.c | 233 ------- src/default.mk | 34 + src/einfo.h | 144 ---- src/einfo.map | 35 - src/fstabinfo.c | 235 ------- src/lib.mk | 55 ++ src/libeinfo.c | 1060 ---------------------------- src/libeinfo/Makefile | 14 + src/libeinfo/einfo.h | 144 ++++ src/libeinfo/einfo.map | 35 + src/libeinfo/libeinfo.c | 1060 ++++++++++++++++++++++++++++ src/librc-daemon.c | 574 --------------- src/librc-depend.c | 979 -------------------------- src/librc-depend.h | 61 -- src/librc-misc.c | 245 ------- src/librc-strlist.c | 230 ------ src/librc.c | 891 ------------------------ src/librc.h | 127 ---- src/librc/Makefile | 14 + src/librc/librc-daemon.c | 574 +++++++++++++++ src/librc/librc-depend.c | 979 ++++++++++++++++++++++++++ src/librc/librc-depend.h | 61 ++ src/librc/librc-misc.c | 245 +++++++ src/librc/librc-strlist.c | 230 ++++++ src/librc/librc.c | 891 ++++++++++++++++++++++++ src/librc/librc.h | 127 ++++ src/librc/rc.h | 446 ++++++++++++ src/mountinfo.c | 464 ------------- src/ncurses.mk | 3 + src/os.mk | 24 + src/pam.mk | 6 + src/prog.mk | 18 + src/rc-depend.c | 192 ------ src/rc-logger.c | 258 ------- src/rc-logger.h | 32 - src/rc-misc.c | 353 ---------- src/rc-plugin.c | 241 ------- src/rc-plugin.h | 55 -- src/rc-status.c | 197 ------ src/rc-update.c | 272 -------- src/rc.c | 1574 ------------------------------------------ src/rc.h | 446 ------------ src/rc.map | 58 -- src/rc/_usage.c | 55 ++ src/rc/_usage.h | 53 ++ src/rc/builtins.h | 39 ++ src/rc/checkpath.c | 233 +++++++ src/rc/fstabinfo.c | 235 +++++++ src/rc/mountinfo.c | 464 +++++++++++++ src/rc/rc-depend.c | 192 ++++++ src/rc/rc-logger.c | 258 +++++++ src/rc/rc-logger.h | 32 + src/rc/rc-misc.c | 353 ++++++++++ src/rc/rc-plugin.c | 241 +++++++ src/rc/rc-plugin.h | 55 ++ src/rc/rc-status.c | 197 ++++++ src/rc/rc-update.c | 272 ++++++++ src/rc/rc.c | 1574 ++++++++++++++++++++++++++++++++++++++++++ src/rc/rc.map | 58 ++ src/rc/runscript.c | 1311 +++++++++++++++++++++++++++++++++++ src/rc/start-stop-daemon.c | 1070 ++++++++++++++++++++++++++++ src/rc/start-stop-daemon.pam | 6 + src/runscript.c | 1311 ----------------------------------- src/start-stop-daemon.c | 1070 ---------------------------- src/start-stop-daemon.pam | 6 - src/termcap.mk | 3 + subdir.mk | 25 + 78 files changed, 11711 insertions(+), 11506 deletions(-) create mode 100644 src/.mk delete mode 100644 src/Makefile. delete mode 100644 src/Makefile.BSD delete mode 100644 src/Makefile.Linux delete mode 100644 src/Makefile.ncurses delete mode 100644 src/Makefile.pam delete mode 100644 src/Makefile.termcap delete mode 100644 src/_usage.c delete mode 100644 src/_usage.h delete mode 100644 src/builtins.h create mode 100644 src/cc.mk delete mode 100644 src/checkpath.c create mode 100644 src/default.mk delete mode 100644 src/einfo.h delete mode 100644 src/einfo.map delete mode 100644 src/fstabinfo.c create mode 100644 src/lib.mk delete mode 100644 src/libeinfo.c create mode 100644 src/libeinfo/Makefile create mode 100644 src/libeinfo/einfo.h create mode 100644 src/libeinfo/einfo.map create mode 100644 src/libeinfo/libeinfo.c delete mode 100644 src/librc-daemon.c delete mode 100644 src/librc-depend.c delete mode 100644 src/librc-depend.h delete mode 100644 src/librc-misc.c delete mode 100644 src/librc-strlist.c delete mode 100644 src/librc.c delete mode 100644 src/librc.h create mode 100644 src/librc/Makefile create mode 100644 src/librc/librc-daemon.c create mode 100644 src/librc/librc-depend.c create mode 100644 src/librc/librc-depend.h create mode 100644 src/librc/librc-misc.c create mode 100644 src/librc/librc-strlist.c create mode 100644 src/librc/librc.c create mode 100644 src/librc/librc.h create mode 100644 src/librc/rc.h delete mode 100644 src/mountinfo.c create mode 100644 src/ncurses.mk create mode 100644 src/os.mk create mode 100644 src/pam.mk create mode 100644 src/prog.mk delete mode 100644 src/rc-depend.c delete mode 100644 src/rc-logger.c delete mode 100644 src/rc-logger.h delete mode 100644 src/rc-misc.c delete mode 100644 src/rc-plugin.c delete mode 100644 src/rc-plugin.h delete mode 100644 src/rc-status.c delete mode 100644 src/rc-update.c delete mode 100644 src/rc.c delete mode 100644 src/rc.h delete mode 100644 src/rc.map create mode 100644 src/rc/_usage.c create mode 100644 src/rc/_usage.h create mode 100644 src/rc/builtins.h create mode 100644 src/rc/checkpath.c create mode 100644 src/rc/fstabinfo.c create mode 100644 src/rc/mountinfo.c create mode 100644 src/rc/rc-depend.c create mode 100644 src/rc/rc-logger.c create mode 100644 src/rc/rc-logger.h create mode 100644 src/rc/rc-misc.c create mode 100644 src/rc/rc-plugin.c create mode 100644 src/rc/rc-plugin.h create mode 100644 src/rc/rc-status.c create mode 100644 src/rc/rc-update.c create mode 100644 src/rc/rc.c create mode 100644 src/rc/rc.map create mode 100644 src/rc/runscript.c create mode 100644 src/rc/start-stop-daemon.c create mode 100644 src/rc/start-stop-daemon.pam delete mode 100644 src/runscript.c delete mode 100644 src/start-stop-daemon.c delete mode 100644 src/start-stop-daemon.pam create mode 100644 src/termcap.mk create mode 100644 subdir.mk diff --git a/src/.mk b/src/.mk new file mode 100644 index 00000000..e69de29b diff --git a/src/Makefile. b/src/Makefile. deleted file mode 100644 index baa0cf1c..00000000 --- a/src/Makefile. +++ /dev/null @@ -1,4 +0,0 @@ -# Empty Makefile so that we can do this -#include Makefile.$(FOO) -# where FOO is unset. -# This is needed as not all make implementations can optionally include a Makefile diff --git a/src/Makefile.BSD b/src/Makefile.BSD deleted file mode 100644 index a9ef9791..00000000 --- a/src/Makefile.BSD +++ /dev/null @@ -1,2 +0,0 @@ -LDLIBS_LIBRC += -lkvm -LDLIBS_RC += -lkvm diff --git a/src/Makefile.Linux b/src/Makefile.Linux deleted file mode 100644 index 96a80ab9..00000000 --- a/src/Makefile.Linux +++ /dev/null @@ -1,2 +0,0 @@ -LDLIBS_RC += -Wl,-Bdynamic -ldl -CPPFLAGS += -D_BSD_SOURCE -D_XOPEN_SOURCE=500 diff --git a/src/Makefile.ncurses b/src/Makefile.ncurses deleted file mode 100644 index 204325dd..00000000 --- a/src/Makefile.ncurses +++ /dev/null @@ -1,2 +0,0 @@ -LIBTERMCAP = -lncurses -include Makefile.termcap diff --git a/src/Makefile.pam b/src/Makefile.pam deleted file mode 100644 index 5f8ff569..00000000 --- a/src/Makefile.pam +++ /dev/null @@ -1,2 +0,0 @@ -CPPFLAGS_SSD = -DHAVE_PAM -LDLIBS_RC += -lpam diff --git a/src/Makefile.termcap b/src/Makefile.termcap deleted file mode 100644 index 10f19865..00000000 --- a/src/Makefile.termcap +++ /dev/null @@ -1,4 +0,0 @@ -LIBTERMCAP ?= -ltermcap -CPPFLAGS_LIBEINFO = -DHAVE_TERMCAP -LDLIBS_LIBEINFO += $(LIBTERMCAP) -LDLIBS_RC += $(LIBTERMCAP) diff --git a/src/_usage.c b/src/_usage.c deleted file mode 100644 index b079e320..00000000 --- a/src/_usage.c +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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]" }; - 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/_usage.h b/src/_usage.h deleted file mode 100644 index e4a5fa3b..00000000 --- a/src/_usage.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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/builtins.h b/src/builtins.h deleted file mode 100644 index 43a4d9f9..00000000 --- a/src/builtins.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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/cc.mk b/src/cc.mk new file mode 100644 index 00000000..d3c96a65 --- /dev/null +++ b/src/cc.mk @@ -0,0 +1,25 @@ +# Copyright 2008 Roy Marples + +# Setup some good default CFLAGS + +CFLAGS ?= -O2 -pipe + +# GNU Make way of detecting gcc flags we can use +check_gcc=$(shell if $(CC) $(1) -S -o /dev/null -xc /dev/null >/dev/null 2>&1; \ + then echo "$(1)"; else echo "$(2)"; fi) + +# pmake check for extra cflags +WEXTRA != for x in -Wdeclaration-after-statement -Wsequence-point -Wextra; do \ + if $(CC) $$x -S -o /dev/null -xc /dev/null >/dev/null 2>&1; \ + then echo -n "$$x "; fi \ + done + +# Loads of nice flags to ensure our code is good +CFLAGS += -pedantic -std=c99 \ + -Wall -Wunused -Wimplicit -Wshadow -Wformat=2 \ + -Wmissing-declarations -Wno-missing-prototypes -Wwrite-strings \ + -Wbad-function-cast -Wnested-externs -Wcomment -Winline \ + -Wchar-subscripts -Wcast-align -Wno-format-nonliteral \ + $(call check_gcc, -Wdeclaration-after-statement) \ + $(call check_gcc, -Wsequence-point) \ + $(call check_gcc, -Wextra) $(WEXTRA) diff --git a/src/checkpath.c b/src/checkpath.c deleted file mode 100644 index 612a0769..00000000 --- a/src/checkpath.c +++ /dev/null @@ -1,233 +0,0 @@ -/* - 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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/default.mk b/src/default.mk new file mode 100644 index 00000000..10f4fcb2 --- /dev/null +++ b/src/default.mk @@ -0,0 +1,34 @@ +# Copyright 2007-2008 Roy Marples + +CC ?= gcc +AR ?= ar +RANLIB ?= ranlib +CFLAGS += -O2 -pipe +LDFLAGS += -L. +PICFLAG = -fPIC + +# GNU Make way of detecting gcc flags we can use +check_gcc=$(shell if $(CC) $(1) -S -o /dev/null -xc /dev/null >/dev/null 2>&1; \ + then echo "$(1)"; else echo "$(2)"; fi) + +# pmake check for extra cflags +WEXTRA != for x in -Wdeclaration-after-statement -Wsequence-point -Wextra; do \ + if $(CC) $$x -S -o /dev/null -xc /dev/null >/dev/null 2>&1; \ + then echo -n "$$x "; fi \ + done + +# Loads of nice flags to ensure our code is good +CFLAGS += -pedantic -std=c99 \ + -Wall -Wunused -Wimplicit -Wshadow -Wformat=2 \ + -Wmissing-declarations -Wno-missing-prototypes -Wwrite-strings \ + -Wbad-function-cast -Wnested-externs -Wcomment -Winline \ + -Wchar-subscripts -Wcast-align -Wno-format-nonliteral \ + $(call check_gcc, -Wdeclaration-after-statement) \ + $(call check_gcc, -Wsequence-point) \ + $(call check_gcc, -Wextra) $(WEXTRA) + +# For debugging. -Werror is pointless due to ISO C issues with dlsym +#CFLAGS += -ggdb + +TOPDIR = .. +include $(TOPDIR)/default.mk diff --git a/src/einfo.h b/src/einfo.h deleted file mode 100644 index 2720e458..00000000 --- a/src/einfo.h +++ /dev/null @@ -1,144 +0,0 @@ -/* - * 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 __EINFO_H__ -#define __EINFO_H__ - -#ifdef __GNUC__ -# define __EINFO_PRINTF __attribute__ ((__format__ (__printf__, 1, 2))) -# define __EINFO_XPRINTF __attribute__ ((__noreturn__, __format__ (__printf__, 1, 2))) -# define __EEND_PRINTF __attribute__ ((__format__ (__printf__, 2, 3))) -#else -# define __EINFO_PRINTF -# define __EINFO_XPRINTF -# define __EEND_PRINTF -#endif - -#include -#include - -/* Although OpenRC requires C99, linking to us should not. */ -#ifdef restrict -# define __EINFO_RESTRICT restrict -#else -# ifdef __restrict -# define __EINFO_RESTRICT __restrict -# else -# define __EINFO_RESTRICT -# endif -#endif - -/*! @brief Color types to use */ -typedef enum -{ - ECOLOR_NORMAL = 1, - ECOLOR_GOOD = 2, - ECOLOR_WARN = 3, - ECOLOR_BAD = 4, - ECOLOR_HILITE = 5, - ECOLOR_BRACKET = 6 -} einfo_color_t; - -/*! @brief Returns the ASCII code for the color */ -const char *ecolor (einfo_color_t); - -/*! @brief Writes to syslog. */ -void elog (int __level, const char * __EINFO_RESTRICT __fmt, ...) __EEND_PRINTF; - -/*! - * @brief Display informational messages. - * - * The einfo family of functions display messages in a consistent manner - * across applications. Basically they prefix the message with - * " * ". If the terminal can handle color then we color the * based on - * the command used. Otherwise we are identical to the printf function. - * - * - einfo - green - * - ewarn - yellow - * - eerror - red - * - * The n suffix denotes that no new line should be printed. - * The v suffix means only print if EINFO_VERBOSE is yes. - */ -/*@{*/ -int einfon (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; -int ewarnn (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; -int eerrorn (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; -int einfo (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; -int ewarn (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; -void ewarnx (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_XPRINTF; -int eerror (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; -void eerrorx (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_XPRINTF; - -int einfovn (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; -int ewarnvn (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; -int ebeginvn (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; -int eendvn (int __retval, const char * __EINFO_RESTRICT __fmt, ...) __EEND_PRINTF; -int ewendvn (int __retval, const char * __EINFO_RESTRICT __fmt, ...) __EEND_PRINTF; -int einfov (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; -int ewarnv (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; -/*@}*/ - -/*! @ingroup ebegin - * @brief Display informational messages that may take some time. - * - * Similar to einfo, but we add ... to the end of the message */ -/*@{*/ -int ebeginv (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; -int ebegin (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; -/*@}*/ - -/*! @ingroup eend - * @brief End an ebegin. - * - * If you ebegin, you should eend also. - * eend places [ ok ] or [ !! ] at the end of the terminal line depending on - * retval (0 or ok, anything else for !!) - * - * ebracket allows you to specifiy the position, color and message */ -/*@{*/ -int eend (int __retval, const char * __EINFO_RESTRICT __fmt, ...) __EEND_PRINTF; -int ewend (int __retval, const char * __EINFO_RESTRICT __fmt, ...) __EEND_PRINTF; -void ebracket (int __col, einfo_color_t __color, const char * __EINFO_RESTRICT __msg); - -int eendv (int __retval, const char * __EINFO_RESTRICT __fmt, ...) __EEND_PRINTF; -int ewendv (int __retval, const char * __EINFO_RESTRICT __fmt, ...) __EEND_PRINTF; -/*@}*/ - -/*! @ingroup eindent - * @brief Indents the einfo lines. - * - * For each indent you should outdent when done */ -/*@{*/ -void eindent (void); -void eoutdent (void); -void eindentv (void); -void eoutdentv (void); - -/*! @brief Prefix each einfo line with something */ -void eprefix (const char * __EINFO_RESTRICT __prefix); - -#endif diff --git a/src/einfo.map b/src/einfo.map deleted file mode 100644 index 428a8954..00000000 --- a/src/einfo.map +++ /dev/null @@ -1,35 +0,0 @@ -EINFO_1.0 { -global: - ecolor; - elog; - einfon; - ewarnn; - eerrorn; - einfo; - ewarn; - ewarnx; - eerror; - eerrorx; - einfovn; - ewarnvn; - ebeginvn; - eendvn; - ewendvn; - einfov; - ewarnv; - ebeginv; - ebegin; - eend; - ewend; - ebracket; - eendv; - ewendv; - eindent; - eoutdent; - eindentv; - eoutdentv; - eprefix; - -local: - *; -}; diff --git a/src/fstabinfo.c b/src/fstabinfo.c deleted file mode 100644 index 5f8e469a..00000000 --- a/src/fstabinfo.c +++ /dev/null @@ -1,235 +0,0 @@ -/* - 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 -#include -#include -#include -#include - -/* 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 -#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 -#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/lib.mk b/src/lib.mk new file mode 100644 index 00000000..947492c1 --- /dev/null +++ b/src/lib.mk @@ -0,0 +1,55 @@ +# rules to build a library +# based on FreeBSD's bsd.lib.mk + +# Copyright 2008 Roy Marples + +SHLIB_NAME= lib${LIB}.so.${SHLIB_MAJOR} +SHLIB_LINK= lib${LIB}.so +SHLIBDIR?= /lib +SONAME?= ${SHLIB_NAME} + +OBJS+= ${SRCS:.c=.o} +SOBJS+= ${OBJS:.o=.So} +_LIBS= lib${LIB}.a ${SHLIB_NAME} + +ECHO?= echo +AR?= ar +RANLIB?= ranlib +INSTALL?= install +LIBMODE?= 0444 + +PICFLAG?= -fPIC + +INCDIR?= /usr/include +INCMODE?= 0444 + +.SUFFIXES: .So + +.c.So: + ${CC} ${PICFLAG} -DPIC ${CFLAGS} ${CPPFLAGS} -c $< -o $@ + +all: ${_LIBS} + +lib${LIB}.a: ${OBJS} ${STATICOBJS} + @${ECHO} building static library $@ + ${AR} rc $@ $^ + ${RANLIB} $@ + + +${SHLIB_NAME}: ${SOBJS} + @${ECHO} building shared library $@ + @rm -f $@ ${SHLIB_LINK} + @ln -fs $@ ${SHLIB_LINK} + ${CC} ${LDFLAGS} -shared -Wl,-x \ + -o $@ -Wl,-soname,${SONAME} \ + ${SOBJS} ${LDADD} + +install: + ${INSTALL} -d ${DESTDIR}${SHLIBDIR} + ${INSTALL} -m ${LIBMODE} ${SHLIB_NAME} ${DESTDIR}${SHLIBDIR} + ln -fs ${SHLIB_NAME} ${DESTDIR}${SHLIBDIR}/${SHLIB_LINK} + ${INSTALL} -d ${DESTDIR}${INCDIR} + for x in ${INCS}; do ${INSTALL} -m ${INCMODE} $$x ${DESTDIR}${INCDIR}; done + +clean: + rm -f ${OBJS} ${SOBJS} ${_LIBS} ${SHLIB_LINK} diff --git a/src/libeinfo.c b/src/libeinfo.c deleted file mode 100644 index d80af9c0..00000000 --- a/src/libeinfo.c +++ /dev/null @@ -1,1060 +0,0 @@ -/* - einfo.c - 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 libeinfo_copyright[] = "Copyright (c) 2007-2008 Roy Marples"; - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_TERMCAP -#include -#endif -#include - -#include "einfo.h" -#include "rc.h" -#include "rc-misc.h" - -#include "hidden-visibility.h" -hidden_proto(ecolor) -hidden_proto(ebegin) -hidden_proto(ebeginv) -hidden_proto(ebracket) -hidden_proto(eend) -hidden_proto(eendv) -hidden_proto(eerror) -hidden_proto(eerrorn) -hidden_proto(eerrorx) -hidden_proto(eindent) -hidden_proto(eindentv) -hidden_proto(einfo) -hidden_proto(einfon) -hidden_proto(einfov) -hidden_proto(einfovn) -hidden_proto(elog) -hidden_proto(eoutdent) -hidden_proto(eoutdentv) -hidden_proto(eprefix) -hidden_proto(ewarn) -hidden_proto(ewarnn) -hidden_proto(ewarnv) -hidden_proto(ewarnvn) -hidden_proto(ewarnx) -hidden_proto(ewend) -hidden_proto(ewendv) - - /* Incase we cannot work out how many columns from ioctl, supply a default */ -#define DEFAULT_COLS 80 - -#define OK "ok" -#define NOT_OK "!!" - -/* Number of spaces for an indent */ -#define INDENT_WIDTH 2 - -/* How wide can the indent go? */ -#define INDENT_MAX 40 - -/* Default colours */ -#define GOOD 2 -#define WARN 3 -#define BAD 1 -#define HILITE 6 -#define BRACKET 4 - -/* We fallback to these escape codes if termcap isn't available - * like say /usr isn't mounted */ -#define AF "\033[3%dm" -#define CE "\033[K" -#define CH "\033[%dC" -#define MD "\033[1m" -#define ME "\033[m" -#define UP "\033[A" - -/* A pointer to a string to prefix to einfo/ewarn/eerror messages */ -static const char *_eprefix = NULL; - -/* Buffers and structures to hold the final colours */ -static char ebuffer[100]; -struct ecolor { - einfo_color_t color; - int def; - const char *name; - char *str; -}; -static char nullstr = '\0'; - -static struct ecolor ecolors[] = { - { ECOLOR_GOOD, GOOD, "good", NULL }, - { ECOLOR_WARN, WARN, "warn", NULL }, - { ECOLOR_BAD, BAD, "bad", NULL }, - { ECOLOR_HILITE, HILITE, "hilite", NULL }, - { ECOLOR_BRACKET, BRACKET, "bracket", NULL }, - { ECOLOR_NORMAL, 0, NULL, NULL }, -}; - -static char *flush = NULL; -static char *up = NULL; -static char *goto_column = NULL; - -static const char *term = NULL; -static bool term_is_cons25 = false; - -/* Termcap buffers and pointers - * Static buffers suck hard, but some termcap implementations require them */ -#ifdef HAVE_TERMCAP -static char termcapbuf[2048]; -static char tcapbuf[512]; -#else -/* No curses support, so we hardcode a list of colour capable terms */ -static const char *const color_terms[] = { - "Eterm", - "ansi", - "color-xterm", - "con132x25", - "con132x30", - "con132x43", - "con132x60", - "con80x25", - "con80x28", - "con80x30", - "con80x43", - "con80x50", - "con80x60", - "cons25", - "console", - "cygwin", - "dtterm", - "gnome", - "konsole", - "kterm", - "linux", - "linux-c", - "mach-color", - "mlterm", - "putty", - "rxvt", - "rxvt-cygwin", - "rxvt-cygwin-native", - "rxvt-unicode", - "screen", - "screen-bce", - "screen-w", - "screen.linux", - "vt100", - "xterm", - "xterm-256color", - "xterm-color", - "xterm-debian", - NULL -}; -#endif - -/* strlcat and strlcpy are nice, shame glibc does not define them */ -#ifdef __GLIBC__ -# if ! defined (__UCLIBC__) && ! defined (__dietlibc__) -static size_t strlcat (char *dst, const char *src, size_t size) -{ - char *d = dst; - const char *s = src; - size_t src_n = size; - size_t dst_n; - - while (src_n-- != 0 && *d != '\0') - d++; - dst_n = d - dst; - src_n = size - dst_n; - - if (src_n == 0) - return (dst_n + strlen (src)); - - while (*s != '\0') { - if (src_n != 1) { - *d++ = *s; - src_n--; - } - s++; - } - *d = '\0'; - - return (dst_n + (s - src)); -} - -static size_t strlcpy (char *dst, const char *src, size_t size) -{ - const char *s = src; - size_t n = size; - - if (n && --n) - do { - if (! (*dst++ = *src++)) - break; - } while (--n); - - if (! n) { - if (size) - *dst = '\0'; - while (*src++); - } - - return (src - s - 1); -} -# endif -#endif - -static bool yesno (const char *value) -{ - if (! value) { - errno = ENOENT; - return (false); - } - - if (strcasecmp (value, "yes") == 0 || - strcasecmp (value, "y") == 0 || - strcasecmp (value, "true") == 0 || - strcasecmp (value, "on") == 0 || - strcasecmp (value, "1") == 0) - return (true); - - if (strcasecmp (value, "no") != 0 && - strcasecmp (value, "n") != 0 && - strcasecmp (value, "false") != 0 && - strcasecmp (value, "off") != 0 && - strcasecmp (value, "0") != 0) - errno = EINVAL; - - return (false); -} - -static bool noyes (const char *value) -{ - int serrno = errno; - bool retval; - - errno = 0; - retval = yesno (value); - if (errno == 0) { - retval = ! retval; - errno = serrno; - } - - return (retval); -} - -static bool is_quiet() -{ - return (yesno (getenv ("EINFO_QUIET"))); -} - -static bool is_verbose() -{ - return (yesno (getenv ("EINFO_VERBOSE"))); -} - -/* Fake tgoto call - very crapy, but works for our needs */ -#ifndef HAVE_TERMCAP -static char *tgoto (const char *cap, int a, int b) -{ - static char buf[20]; - - snprintf (buf, sizeof (buf), cap, b, a); - return (buf); -} -#endif - -static bool colour_terminal (FILE * __EINFO_RESTRICT f) -{ - static int in_colour = -1; - char *e; - int c; - const char *_af = NULL; - const char *_ce = NULL; - const char *_ch = NULL; - const char *_md = NULL; - const char *_me = NULL; - const char *_up = NULL; - char tmp[100]; - char *p; - unsigned int i = 0; - - if (f && ! isatty (fileno (f))) - return (false); - - if (noyes (getenv ("EINFO_COLOR"))) - return (false); - - if (in_colour == 0) - return (false); - if (in_colour == 1) - return (true); - - term_is_cons25 = false; - - if (! term) { - term = getenv ("TERM"); - if (! term) - return (false); - } - - if (strcmp (term, "cons25") == 0) - term_is_cons25 = true; - -#ifdef HAVE_TERMCAP - /* Check termcap to see if we can do colour or not */ - if (tgetent (termcapbuf, term) == 1) { - char *bp = tcapbuf; - - _af = tgetstr ("AF", &bp); - _ce = tgetstr ("ce", &bp); - _ch = tgetstr ("ch", &bp); - /* Our ch use also works with RI .... for now */ - if (! _ch) - _ch = tgetstr ("RI", &bp); - _md = tgetstr ("md", &bp); - _me = tgetstr ("me", &bp); - _up = tgetstr ("up", &bp); - } - - /* Cheat here as vanilla BSD has the whole termcap info in /usr - * which is not available to us when we boot */ - if (term_is_cons25) { -#else - while (color_terms[i]) { - if (strcmp (color_terms[i], term) == 0) { - in_colour = 1; - } - i++; - } - - if (in_colour != 1) { - in_colour = 0; - return (false); - } -#endif - if (! _af) - _af = AF; - if (! _ce) - _ce = CE; - if (! _ch) - _ch = CH; - if (! _md) - _md = MD; - if (! _me) - _me = ME; - if (! _up) - _up = UP; -#ifdef HAVE_TERMCAP - } - - if (! _af || ! _ce || ! _ch || ! _me || !_md || ! _up) { - in_colour = 0; - return (false); - } -#endif - -#define _GET_CAP(_d, _c) strlcpy (_d, tgoto (_c, 0, 0), sizeof (_d)); -#define _ASSIGN_CAP(_v) { \ - _v = p; \ - p += strlcpy (p, tmp, sizeof (ebuffer) - (p - ebuffer)) + 1; \ -} - - /* Now setup our colours */ - p = ebuffer; - for (i = 0; i < sizeof (ecolors) / sizeof (ecolors[0]); i++) { - tmp[0] = '\0'; - - if (ecolors[i].name) { - const char *bold = _md; - c = ecolors[i].def; - - /* See if the user wants to override the colour - * We use a :col;bold: format like 2;1: for bold green - * and 1;0: for a normal red */ - if ((e = getenv("EINFO_COLOR"))) { - char *ee = strstr (e, ecolors[i].name); - - if (ee) - ee += strlen (ecolors[i].name); - - if (ee && *ee == '=') { - - char *d = xstrdup (ee + 1); - char *end = strchr (d, ':'); - if (end) - *end = '\0'; - - c = atoi(d); - - end = strchr (d, ';'); - if (end && *++end == '0') - bold = _me; - - free (d); - } - } - strlcpy (tmp, tgoto (bold, 0, 0), sizeof (tmp)); - strlcat (tmp, tgoto (_af, 0, c & 0x07), sizeof (tmp)); - } else - _GET_CAP (tmp, _me); - - if (tmp[0]) - _ASSIGN_CAP (ecolors[i].str) - else - ecolors[i].str = &nullstr; - } - - _GET_CAP (tmp, _ce) - _ASSIGN_CAP (flush) - _GET_CAP (tmp, _up); - _ASSIGN_CAP (up); - strlcpy (tmp, _ch, sizeof (tmp)); - _ASSIGN_CAP (goto_column); - - in_colour = 1; - return (true); -} - -static int get_term_columns (FILE * __EINFO_RESTRICT stream) -{ - struct winsize ws; - char *env = getenv ("COLUMNS"); - char *p; - int i; - - if (env) { - i = strtol (env, &p, 10); - if (! *p) - return (i); - } - - if (ioctl (fileno (stream), TIOCGWINSZ, &ws) == 0) - return (ws.ws_col); - - return (DEFAULT_COLS); -} - -void eprefix (const char *__EINFO_RESTRICT prefix) -{ - _eprefix = prefix; -} -hidden_def(eprefix) - -static void elogv (int level, const char *__EINFO_RESTRICT fmt, va_list ap) -{ - char *e = getenv ("EINFO_LOG"); - va_list apc; - - if (fmt && e) { - closelog (); - openlog (e, LOG_PID, LOG_DAEMON); - va_copy (apc, ap); - vsyslog (level, fmt, apc); - va_end (apc); - closelog (); - } -} - -void elog (int level, const char *__EINFO_RESTRICT fmt, ...) -{ - va_list ap; - - va_start (ap, fmt); - elogv (level, fmt, ap); - va_end (ap); -} -hidden_def(elog) - -static int _eindent (FILE * __EINFO_RESTRICT stream) -{ - char *env = getenv ("EINFO_INDENT"); - int amount = 0; - char indent[INDENT_MAX]; - - if (env) { - errno = 0; - amount = strtol (env, NULL, 0); - if (errno != 0 || amount < 0) - amount = 0; - else if (amount > INDENT_MAX) - amount = INDENT_MAX; - - if (amount > 0) - memset (indent, ' ', amount); - } - - /* Terminate it */ - memset (indent + amount, 0, 1); - return (fprintf (stream, "%s", indent)); -} - -static const char *_ecolor (FILE * __EINFO_RESTRICT f, einfo_color_t color) -{ - unsigned int i; - - if (! colour_terminal (f)) - return (""); - - for (i = 0; i < sizeof (ecolors) / sizeof (ecolors[0]); i++) { - if (ecolors[i].color == color) - return (ecolors[i].str); - } - - return (""); -} -hidden_def(ecolor) - -const char *ecolor (einfo_color_t color) -{ - FILE *f = stdout; - - /* Try and guess a valid tty */ - if (! isatty (fileno (f))) { - f = stderr; - if (! isatty (fileno (f))) { - f = stdin; - if (! isatty (fileno (f))) - f = NULL; - } - } - - return (_ecolor (f, color)); -} - -#define LASTCMD(_cmd) \ - unsetenv ("EINFO_LASTCMD"); \ - setenv ("EINFO_LASTCMD", _cmd, 1); - -#define EINFOVN(_file, _color) \ -{ \ - char *_e = getenv ("EINFO_LASTCMD"); \ - if (_e && ! colour_terminal (_file) && strcmp (_e, "ewarn") != 0 && \ - _e[strlen (_e) - 1] == 'n') \ - fprintf (_file, "\n"); \ - if (_eprefix) \ - fprintf (_file, "%s%s%s|", _ecolor (_file, _color), _eprefix, _ecolor (_file, ECOLOR_NORMAL)); \ - fprintf (_file, " %s*%s ", _ecolor (_file, _color), _ecolor (_file, ECOLOR_NORMAL)); \ - retval += _eindent (_file); \ -{ \ - va_list _ap; \ - va_copy (_ap, ap); \ - retval += vfprintf (_file, fmt, _ap) + 3; \ - va_end (_ap); \ -} \ - if (colour_terminal (_file)) \ - fprintf (_file, "%s", flush); \ -} - -static int _einfovn (const char *__EINFO_RESTRICT fmt, va_list ap) -{ - int retval = 0; - - EINFOVN (stdout, ECOLOR_GOOD); - return (retval); -} - -static int _ewarnvn (const char *__EINFO_RESTRICT fmt, va_list ap) -{ - int retval = 0; - - EINFOVN (stdout, ECOLOR_WARN); - return (retval); -} - -static int _eerrorvn (const char *__EINFO_RESTRICT fmt, va_list ap) -{ - int retval = 0; - - EINFOVN (stderr, ECOLOR_BAD); - return (retval); -} - -int einfon (const char *__EINFO_RESTRICT fmt, ...) -{ - int retval; - va_list ap; - - if (! fmt || is_quiet ()) - return (0); - - va_start (ap, fmt); - retval = _einfovn (fmt, ap); - va_end (ap); - - LASTCMD ("einfon"); - - return (retval); -} -hidden_def(einfon) - -int ewarnn (const char *__EINFO_RESTRICT fmt, ...) -{ - int retval; - va_list ap; - - if (! fmt || is_quiet ()) - return (0); - - va_start (ap, fmt); - retval = _ewarnvn (fmt, ap); - va_end (ap); - - LASTCMD ("ewarnn"); - - return (retval); -} -hidden_def(ewarnn) - -int eerrorn (const char *__EINFO_RESTRICT fmt, ...) -{ - int retval; - va_list ap; - - va_start (ap, fmt); - retval = _eerrorvn (fmt, ap); - va_end (ap); - - LASTCMD ("errorn"); - - return (retval); -} -hidden_def(eerrorn) - -int einfo (const char *__EINFO_RESTRICT fmt, ...) -{ - int retval; - va_list ap; - - if (! fmt || is_quiet()) - return (0); - - va_start (ap, fmt); - retval = _einfovn (fmt, ap); - retval += printf ("\n"); - va_end (ap); - - LASTCMD ("einfo"); - - return (retval); -} -hidden_def(einfo) - -int ewarn (const char *__EINFO_RESTRICT fmt, ...) -{ - int retval; - va_list ap; - - if (! fmt || is_quiet ()) - return (0); - - va_start (ap, fmt); - elogv (LOG_WARNING, fmt, ap); - retval = _ewarnvn (fmt, ap); - retval += printf ("\n"); - va_end (ap); - - LASTCMD ("ewarn"); - - return (retval); -} -hidden_def(ewarn) - -void ewarnx (const char *__EINFO_RESTRICT fmt, ...) -{ - int retval; - va_list ap; - - if (fmt && ! is_quiet ()) { - va_start (ap, fmt); - elogv (LOG_WARNING, fmt, ap); - retval = _ewarnvn (fmt, ap); - va_end (ap); - retval += printf ("\n"); - } - exit (EXIT_FAILURE); -} -hidden_def(ewarnx) - -int eerror (const char *__EINFO_RESTRICT fmt, ...) -{ - int retval; - va_list ap; - - if (! fmt) - return (0); - - va_start (ap, fmt); - elogv (LOG_ERR, fmt, ap); - retval = _eerrorvn (fmt, ap); - va_end (ap); - retval += fprintf (stderr, "\n"); - - LASTCMD ("eerror"); - - return (retval); -} -hidden_def(eerror) - -void eerrorx (const char *__EINFO_RESTRICT fmt, ...) -{ - va_list ap; - - if (fmt) { - va_start (ap, fmt); - elogv (LOG_ERR, fmt, ap); - _eerrorvn (fmt, ap); - va_end (ap); - fprintf (stderr, "\n"); - } - - exit (EXIT_FAILURE); -} -hidden_def(eerrorx) - -int ebegin (const char *__EINFO_RESTRICT fmt, ...) -{ - int retval; - va_list ap; - - if (! fmt || is_quiet ()) - return (0); - - va_start (ap, fmt); - retval = _einfovn (fmt, ap); - va_end (ap); - retval += printf (" ..."); - if (colour_terminal (stdout)) - retval += printf ("\n"); - - LASTCMD ("ebegin"); - - return (retval); -} -hidden_def(ebegin) - -static void _eend (FILE * __EINFO_RESTRICT fp, int col, einfo_color_t color, - const char *msg) -{ - int i; - int cols; - - if (! msg) - return; - - cols = get_term_columns (fp) - (strlen (msg) + 3); - - /* cons25 is special - we need to remove one char, otherwise things - * do not align properly at all. */ - if (! term) { - term = getenv ("TERM"); - if (term && strcmp (term, "cons25") == 0) - term_is_cons25 = true; - else - term_is_cons25 = false; - } - if (term_is_cons25) - cols--; - - if (cols > 0 && colour_terminal (fp)) { - fprintf (fp, "%s%s %s[%s%s%s]%s\n", up, tgoto (goto_column, 0, cols), - ecolor (ECOLOR_BRACKET), ecolor (color), msg, - ecolor (ECOLOR_BRACKET), ecolor (ECOLOR_NORMAL)); - } else { - if (col > 0) - for (i = 0; i < cols - col; i++) - fprintf (fp, " "); - fprintf (fp, " [%s]\n", msg); - } -} - -static int _do_eend (const char *cmd, int retval, const char *__EINFO_RESTRICT fmt, va_list ap) -{ - int col = 0; - FILE *fp = stdout; - va_list apc; - - if (fmt && retval != 0) { - va_copy (apc, ap); - if (strcmp (cmd, "ewend") == 0) { - col = _ewarnvn (fmt, apc); - fp = stdout; - } else { - col = _eerrorvn (fmt, apc); - fp = stderr; - } - col += fprintf (fp, "\n"); - va_end (apc); - } - - _eend (fp, col, - retval == 0 ? ECOLOR_GOOD : ECOLOR_BAD, - retval == 0 ? OK : NOT_OK); - return (retval); -} - -int eend (int retval, const char *__EINFO_RESTRICT fmt, ...) -{ - va_list ap; - - if (is_quiet ()) - return (retval); - - va_start (ap, fmt); - _do_eend ("eend", retval, fmt, ap); - va_end (ap); - - LASTCMD ("eend"); - - return (retval); -} -hidden_def(eend) - -int ewend (int retval, const char *__EINFO_RESTRICT fmt, ...) -{ - va_list ap; - - if (is_quiet ()) - return (retval); - - va_start (ap, fmt); - _do_eend ("ewend", retval, fmt, ap); - va_end (ap); - - LASTCMD ("ewend"); - - return (retval); -} -hidden_def(ewend) - -void ebracket (int col, einfo_color_t color, const char *msg) -{ - _eend (stdout, col, color, msg); -} -hidden_def(ebracket) - -void eindent (void) -{ - char *env = getenv ("EINFO_INDENT"); - int amount = 0; - char num[10]; - - if (env) { - errno = 0; - amount = strtol (env, NULL, 0); - if (errno != 0) - amount = 0; - } - - amount += INDENT_WIDTH; - if (amount > INDENT_MAX) - amount = INDENT_MAX; - - snprintf (num, 10, "%08d", amount); - setenv ("EINFO_INDENT", num, 1); -} -hidden_def(eindent) - -void eoutdent (void) -{ - char *env = getenv ("EINFO_INDENT"); - int amount = 0; - char num[10]; - - if (! env) - return; - - errno = 0; - amount = strtol (env, NULL, 0); - if (errno != 0) - amount = 0; - else - amount -= INDENT_WIDTH; - - if (amount <= 0) - unsetenv ("EINFO_EINDENT"); - else { - snprintf (num, 10, "%08d", amount); - setenv ("EINFO_EINDENT", num, 1); - } -} -hidden_def(eoutdent) - -int einfovn (const char *__EINFO_RESTRICT fmt, ...) -{ - int retval; - va_list ap; - - if (! fmt || ! is_verbose ()) - return (0); - - va_start (ap, fmt); - retval = _einfovn (fmt, ap); - va_end (ap); - - LASTCMD ("einfovn"); - - return (retval); -} -hidden_def(einfovn) - -int ewarnvn (const char *__EINFO_RESTRICT fmt, ...) -{ - int retval; - va_list ap; - - if (! fmt || ! is_verbose ()) - return (0); - - va_start (ap, fmt); - retval = _ewarnvn (fmt, ap); - va_end (ap); - - LASTCMD ("ewarnvn"); - - return (retval); -} -hidden_def(ewarnvn) - -int einfov (const char *__EINFO_RESTRICT fmt, ...) -{ - int retval; - va_list ap; - - if (! fmt || ! is_verbose ()) - return (0); - - va_start (ap, fmt); - retval = _einfovn (fmt, ap); - retval += printf ("\n"); - va_end (ap); - - LASTCMD ("einfov"); - - return (retval); -} -hidden_def(einfov) - -int ewarnv (const char *__EINFO_RESTRICT fmt, ...) -{ - int retval; - va_list ap; - - if (! fmt || ! is_verbose ()) - return (0); - - va_start (ap, fmt); - retval = _ewarnvn (fmt, ap); - retval += printf ("\n"); - va_end (ap); - - LASTCMD ("ewarnv"); - - return (retval); -} -hidden_def(ewarnv) - -int ebeginv (const char *__EINFO_RESTRICT fmt, ...) -{ - int retval; - va_list ap; - - if (! fmt || ! is_verbose ()) - return (0); - - va_start (ap, fmt); - retval = _einfovn (fmt, ap); - retval += printf (" ..."); - if (colour_terminal (stdout)) - retval += printf ("\n"); - va_end (ap); - - LASTCMD ("ebeginv"); - - return (retval); -} -hidden_def(ebeginv) - -int eendv (int retval, const char *__EINFO_RESTRICT fmt, ...) -{ - va_list ap; - - if (! is_verbose ()) - return (0); - - va_start (ap, fmt); - _do_eend ("eendv", retval, fmt, ap); - va_end (ap); - - LASTCMD ("eendv"); - - return (retval); -} -hidden_def(eendv) - -int ewendv (int retval, const char *__EINFO_RESTRICT fmt, ...) -{ - va_list ap; - - if (! is_verbose ()) - return (0); - - va_start (ap, fmt); - _do_eend ("ewendv", retval, fmt, ap); - va_end (ap); - - LASTCMD ("ewendv"); - - return (retval); -} -hidden_def(ewendv) - -void eindentv (void) -{ - if (is_verbose ()) - eindent (); -} -hidden_def(eindentv) - -void eoutdentv (void) -{ - if (is_verbose ()) - eoutdent (); -} -hidden_def(eoutdentv) diff --git a/src/libeinfo/Makefile b/src/libeinfo/Makefile new file mode 100644 index 00000000..b6826340 --- /dev/null +++ b/src/libeinfo/Makefile @@ -0,0 +1,14 @@ +TOPDIR= .. +include $(TOPDIR)/os.mk + +LIB= einfo +SHLIB_MAJOR= 1 +SRCS= libeinfo.c +INCS= einfo.h + +SHLIBDIR= /${LIBNAME} + +include $(TOPDIR)/cc.mk +include $(TOPDIR)/lib.mk +include $(TOPDIR)/$(TERMCAP).mk + diff --git a/src/libeinfo/einfo.h b/src/libeinfo/einfo.h new file mode 100644 index 00000000..2720e458 --- /dev/null +++ b/src/libeinfo/einfo.h @@ -0,0 +1,144 @@ +/* + * 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 __EINFO_H__ +#define __EINFO_H__ + +#ifdef __GNUC__ +# define __EINFO_PRINTF __attribute__ ((__format__ (__printf__, 1, 2))) +# define __EINFO_XPRINTF __attribute__ ((__noreturn__, __format__ (__printf__, 1, 2))) +# define __EEND_PRINTF __attribute__ ((__format__ (__printf__, 2, 3))) +#else +# define __EINFO_PRINTF +# define __EINFO_XPRINTF +# define __EEND_PRINTF +#endif + +#include +#include + +/* Although OpenRC requires C99, linking to us should not. */ +#ifdef restrict +# define __EINFO_RESTRICT restrict +#else +# ifdef __restrict +# define __EINFO_RESTRICT __restrict +# else +# define __EINFO_RESTRICT +# endif +#endif + +/*! @brief Color types to use */ +typedef enum +{ + ECOLOR_NORMAL = 1, + ECOLOR_GOOD = 2, + ECOLOR_WARN = 3, + ECOLOR_BAD = 4, + ECOLOR_HILITE = 5, + ECOLOR_BRACKET = 6 +} einfo_color_t; + +/*! @brief Returns the ASCII code for the color */ +const char *ecolor (einfo_color_t); + +/*! @brief Writes to syslog. */ +void elog (int __level, const char * __EINFO_RESTRICT __fmt, ...) __EEND_PRINTF; + +/*! + * @brief Display informational messages. + * + * The einfo family of functions display messages in a consistent manner + * across applications. Basically they prefix the message with + * " * ". If the terminal can handle color then we color the * based on + * the command used. Otherwise we are identical to the printf function. + * + * - einfo - green + * - ewarn - yellow + * - eerror - red + * + * The n suffix denotes that no new line should be printed. + * The v suffix means only print if EINFO_VERBOSE is yes. + */ +/*@{*/ +int einfon (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; +int ewarnn (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; +int eerrorn (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; +int einfo (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; +int ewarn (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; +void ewarnx (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_XPRINTF; +int eerror (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; +void eerrorx (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_XPRINTF; + +int einfovn (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; +int ewarnvn (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; +int ebeginvn (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; +int eendvn (int __retval, const char * __EINFO_RESTRICT __fmt, ...) __EEND_PRINTF; +int ewendvn (int __retval, const char * __EINFO_RESTRICT __fmt, ...) __EEND_PRINTF; +int einfov (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; +int ewarnv (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; +/*@}*/ + +/*! @ingroup ebegin + * @brief Display informational messages that may take some time. + * + * Similar to einfo, but we add ... to the end of the message */ +/*@{*/ +int ebeginv (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; +int ebegin (const char * __EINFO_RESTRICT __fmt, ...) __EINFO_PRINTF; +/*@}*/ + +/*! @ingroup eend + * @brief End an ebegin. + * + * If you ebegin, you should eend also. + * eend places [ ok ] or [ !! ] at the end of the terminal line depending on + * retval (0 or ok, anything else for !!) + * + * ebracket allows you to specifiy the position, color and message */ +/*@{*/ +int eend (int __retval, const char * __EINFO_RESTRICT __fmt, ...) __EEND_PRINTF; +int ewend (int __retval, const char * __EINFO_RESTRICT __fmt, ...) __EEND_PRINTF; +void ebracket (int __col, einfo_color_t __color, const char * __EINFO_RESTRICT __msg); + +int eendv (int __retval, const char * __EINFO_RESTRICT __fmt, ...) __EEND_PRINTF; +int ewendv (int __retval, const char * __EINFO_RESTRICT __fmt, ...) __EEND_PRINTF; +/*@}*/ + +/*! @ingroup eindent + * @brief Indents the einfo lines. + * + * For each indent you should outdent when done */ +/*@{*/ +void eindent (void); +void eoutdent (void); +void eindentv (void); +void eoutdentv (void); + +/*! @brief Prefix each einfo line with something */ +void eprefix (const char * __EINFO_RESTRICT __prefix); + +#endif diff --git a/src/libeinfo/einfo.map b/src/libeinfo/einfo.map new file mode 100644 index 00000000..428a8954 --- /dev/null +++ b/src/libeinfo/einfo.map @@ -0,0 +1,35 @@ +EINFO_1.0 { +global: + ecolor; + elog; + einfon; + ewarnn; + eerrorn; + einfo; + ewarn; + ewarnx; + eerror; + eerrorx; + einfovn; + ewarnvn; + ebeginvn; + eendvn; + ewendvn; + einfov; + ewarnv; + ebeginv; + ebegin; + eend; + ewend; + ebracket; + eendv; + ewendv; + eindent; + eoutdent; + eindentv; + eoutdentv; + eprefix; + +local: + *; +}; diff --git a/src/libeinfo/libeinfo.c b/src/libeinfo/libeinfo.c new file mode 100644 index 00000000..d80af9c0 --- /dev/null +++ b/src/libeinfo/libeinfo.c @@ -0,0 +1,1060 @@ +/* + einfo.c + 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 libeinfo_copyright[] = "Copyright (c) 2007-2008 Roy Marples"; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_TERMCAP +#include +#endif +#include + +#include "einfo.h" +#include "rc.h" +#include "rc-misc.h" + +#include "hidden-visibility.h" +hidden_proto(ecolor) +hidden_proto(ebegin) +hidden_proto(ebeginv) +hidden_proto(ebracket) +hidden_proto(eend) +hidden_proto(eendv) +hidden_proto(eerror) +hidden_proto(eerrorn) +hidden_proto(eerrorx) +hidden_proto(eindent) +hidden_proto(eindentv) +hidden_proto(einfo) +hidden_proto(einfon) +hidden_proto(einfov) +hidden_proto(einfovn) +hidden_proto(elog) +hidden_proto(eoutdent) +hidden_proto(eoutdentv) +hidden_proto(eprefix) +hidden_proto(ewarn) +hidden_proto(ewarnn) +hidden_proto(ewarnv) +hidden_proto(ewarnvn) +hidden_proto(ewarnx) +hidden_proto(ewend) +hidden_proto(ewendv) + + /* Incase we cannot work out how many columns from ioctl, supply a default */ +#define DEFAULT_COLS 80 + +#define OK "ok" +#define NOT_OK "!!" + +/* Number of spaces for an indent */ +#define INDENT_WIDTH 2 + +/* How wide can the indent go? */ +#define INDENT_MAX 40 + +/* Default colours */ +#define GOOD 2 +#define WARN 3 +#define BAD 1 +#define HILITE 6 +#define BRACKET 4 + +/* We fallback to these escape codes if termcap isn't available + * like say /usr isn't mounted */ +#define AF "\033[3%dm" +#define CE "\033[K" +#define CH "\033[%dC" +#define MD "\033[1m" +#define ME "\033[m" +#define UP "\033[A" + +/* A pointer to a string to prefix to einfo/ewarn/eerror messages */ +static const char *_eprefix = NULL; + +/* Buffers and structures to hold the final colours */ +static char ebuffer[100]; +struct ecolor { + einfo_color_t color; + int def; + const char *name; + char *str; +}; +static char nullstr = '\0'; + +static struct ecolor ecolors[] = { + { ECOLOR_GOOD, GOOD, "good", NULL }, + { ECOLOR_WARN, WARN, "warn", NULL }, + { ECOLOR_BAD, BAD, "bad", NULL }, + { ECOLOR_HILITE, HILITE, "hilite", NULL }, + { ECOLOR_BRACKET, BRACKET, "bracket", NULL }, + { ECOLOR_NORMAL, 0, NULL, NULL }, +}; + +static char *flush = NULL; +static char *up = NULL; +static char *goto_column = NULL; + +static const char *term = NULL; +static bool term_is_cons25 = false; + +/* Termcap buffers and pointers + * Static buffers suck hard, but some termcap implementations require them */ +#ifdef HAVE_TERMCAP +static char termcapbuf[2048]; +static char tcapbuf[512]; +#else +/* No curses support, so we hardcode a list of colour capable terms */ +static const char *const color_terms[] = { + "Eterm", + "ansi", + "color-xterm", + "con132x25", + "con132x30", + "con132x43", + "con132x60", + "con80x25", + "con80x28", + "con80x30", + "con80x43", + "con80x50", + "con80x60", + "cons25", + "console", + "cygwin", + "dtterm", + "gnome", + "konsole", + "kterm", + "linux", + "linux-c", + "mach-color", + "mlterm", + "putty", + "rxvt", + "rxvt-cygwin", + "rxvt-cygwin-native", + "rxvt-unicode", + "screen", + "screen-bce", + "screen-w", + "screen.linux", + "vt100", + "xterm", + "xterm-256color", + "xterm-color", + "xterm-debian", + NULL +}; +#endif + +/* strlcat and strlcpy are nice, shame glibc does not define them */ +#ifdef __GLIBC__ +# if ! defined (__UCLIBC__) && ! defined (__dietlibc__) +static size_t strlcat (char *dst, const char *src, size_t size) +{ + char *d = dst; + const char *s = src; + size_t src_n = size; + size_t dst_n; + + while (src_n-- != 0 && *d != '\0') + d++; + dst_n = d - dst; + src_n = size - dst_n; + + if (src_n == 0) + return (dst_n + strlen (src)); + + while (*s != '\0') { + if (src_n != 1) { + *d++ = *s; + src_n--; + } + s++; + } + *d = '\0'; + + return (dst_n + (s - src)); +} + +static size_t strlcpy (char *dst, const char *src, size_t size) +{ + const char *s = src; + size_t n = size; + + if (n && --n) + do { + if (! (*dst++ = *src++)) + break; + } while (--n); + + if (! n) { + if (size) + *dst = '\0'; + while (*src++); + } + + return (src - s - 1); +} +# endif +#endif + +static bool yesno (const char *value) +{ + if (! value) { + errno = ENOENT; + return (false); + } + + if (strcasecmp (value, "yes") == 0 || + strcasecmp (value, "y") == 0 || + strcasecmp (value, "true") == 0 || + strcasecmp (value, "on") == 0 || + strcasecmp (value, "1") == 0) + return (true); + + if (strcasecmp (value, "no") != 0 && + strcasecmp (value, "n") != 0 && + strcasecmp (value, "false") != 0 && + strcasecmp (value, "off") != 0 && + strcasecmp (value, "0") != 0) + errno = EINVAL; + + return (false); +} + +static bool noyes (const char *value) +{ + int serrno = errno; + bool retval; + + errno = 0; + retval = yesno (value); + if (errno == 0) { + retval = ! retval; + errno = serrno; + } + + return (retval); +} + +static bool is_quiet() +{ + return (yesno (getenv ("EINFO_QUIET"))); +} + +static bool is_verbose() +{ + return (yesno (getenv ("EINFO_VERBOSE"))); +} + +/* Fake tgoto call - very crapy, but works for our needs */ +#ifndef HAVE_TERMCAP +static char *tgoto (const char *cap, int a, int b) +{ + static char buf[20]; + + snprintf (buf, sizeof (buf), cap, b, a); + return (buf); +} +#endif + +static bool colour_terminal (FILE * __EINFO_RESTRICT f) +{ + static int in_colour = -1; + char *e; + int c; + const char *_af = NULL; + const char *_ce = NULL; + const char *_ch = NULL; + const char *_md = NULL; + const char *_me = NULL; + const char *_up = NULL; + char tmp[100]; + char *p; + unsigned int i = 0; + + if (f && ! isatty (fileno (f))) + return (false); + + if (noyes (getenv ("EINFO_COLOR"))) + return (false); + + if (in_colour == 0) + return (false); + if (in_colour == 1) + return (true); + + term_is_cons25 = false; + + if (! term) { + term = getenv ("TERM"); + if (! term) + return (false); + } + + if (strcmp (term, "cons25") == 0) + term_is_cons25 = true; + +#ifdef HAVE_TERMCAP + /* Check termcap to see if we can do colour or not */ + if (tgetent (termcapbuf, term) == 1) { + char *bp = tcapbuf; + + _af = tgetstr ("AF", &bp); + _ce = tgetstr ("ce", &bp); + _ch = tgetstr ("ch", &bp); + /* Our ch use also works with RI .... for now */ + if (! _ch) + _ch = tgetstr ("RI", &bp); + _md = tgetstr ("md", &bp); + _me = tgetstr ("me", &bp); + _up = tgetstr ("up", &bp); + } + + /* Cheat here as vanilla BSD has the whole termcap info in /usr + * which is not available to us when we boot */ + if (term_is_cons25) { +#else + while (color_terms[i]) { + if (strcmp (color_terms[i], term) == 0) { + in_colour = 1; + } + i++; + } + + if (in_colour != 1) { + in_colour = 0; + return (false); + } +#endif + if (! _af) + _af = AF; + if (! _ce) + _ce = CE; + if (! _ch) + _ch = CH; + if (! _md) + _md = MD; + if (! _me) + _me = ME; + if (! _up) + _up = UP; +#ifdef HAVE_TERMCAP + } + + if (! _af || ! _ce || ! _ch || ! _me || !_md || ! _up) { + in_colour = 0; + return (false); + } +#endif + +#define _GET_CAP(_d, _c) strlcpy (_d, tgoto (_c, 0, 0), sizeof (_d)); +#define _ASSIGN_CAP(_v) { \ + _v = p; \ + p += strlcpy (p, tmp, sizeof (ebuffer) - (p - ebuffer)) + 1; \ +} + + /* Now setup our colours */ + p = ebuffer; + for (i = 0; i < sizeof (ecolors) / sizeof (ecolors[0]); i++) { + tmp[0] = '\0'; + + if (ecolors[i].name) { + const char *bold = _md; + c = ecolors[i].def; + + /* See if the user wants to override the colour + * We use a :col;bold: format like 2;1: for bold green + * and 1;0: for a normal red */ + if ((e = getenv("EINFO_COLOR"))) { + char *ee = strstr (e, ecolors[i].name); + + if (ee) + ee += strlen (ecolors[i].name); + + if (ee && *ee == '=') { + + char *d = xstrdup (ee + 1); + char *end = strchr (d, ':'); + if (end) + *end = '\0'; + + c = atoi(d); + + end = strchr (d, ';'); + if (end && *++end == '0') + bold = _me; + + free (d); + } + } + strlcpy (tmp, tgoto (bold, 0, 0), sizeof (tmp)); + strlcat (tmp, tgoto (_af, 0, c & 0x07), sizeof (tmp)); + } else + _GET_CAP (tmp, _me); + + if (tmp[0]) + _ASSIGN_CAP (ecolors[i].str) + else + ecolors[i].str = &nullstr; + } + + _GET_CAP (tmp, _ce) + _ASSIGN_CAP (flush) + _GET_CAP (tmp, _up); + _ASSIGN_CAP (up); + strlcpy (tmp, _ch, sizeof (tmp)); + _ASSIGN_CAP (goto_column); + + in_colour = 1; + return (true); +} + +static int get_term_columns (FILE * __EINFO_RESTRICT stream) +{ + struct winsize ws; + char *env = getenv ("COLUMNS"); + char *p; + int i; + + if (env) { + i = strtol (env, &p, 10); + if (! *p) + return (i); + } + + if (ioctl (fileno (stream), TIOCGWINSZ, &ws) == 0) + return (ws.ws_col); + + return (DEFAULT_COLS); +} + +void eprefix (const char *__EINFO_RESTRICT prefix) +{ + _eprefix = prefix; +} +hidden_def(eprefix) + +static void elogv (int level, const char *__EINFO_RESTRICT fmt, va_list ap) +{ + char *e = getenv ("EINFO_LOG"); + va_list apc; + + if (fmt && e) { + closelog (); + openlog (e, LOG_PID, LOG_DAEMON); + va_copy (apc, ap); + vsyslog (level, fmt, apc); + va_end (apc); + closelog (); + } +} + +void elog (int level, const char *__EINFO_RESTRICT fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + elogv (level, fmt, ap); + va_end (ap); +} +hidden_def(elog) + +static int _eindent (FILE * __EINFO_RESTRICT stream) +{ + char *env = getenv ("EINFO_INDENT"); + int amount = 0; + char indent[INDENT_MAX]; + + if (env) { + errno = 0; + amount = strtol (env, NULL, 0); + if (errno != 0 || amount < 0) + amount = 0; + else if (amount > INDENT_MAX) + amount = INDENT_MAX; + + if (amount > 0) + memset (indent, ' ', amount); + } + + /* Terminate it */ + memset (indent + amount, 0, 1); + return (fprintf (stream, "%s", indent)); +} + +static const char *_ecolor (FILE * __EINFO_RESTRICT f, einfo_color_t color) +{ + unsigned int i; + + if (! colour_terminal (f)) + return (""); + + for (i = 0; i < sizeof (ecolors) / sizeof (ecolors[0]); i++) { + if (ecolors[i].color == color) + return (ecolors[i].str); + } + + return (""); +} +hidden_def(ecolor) + +const char *ecolor (einfo_color_t color) +{ + FILE *f = stdout; + + /* Try and guess a valid tty */ + if (! isatty (fileno (f))) { + f = stderr; + if (! isatty (fileno (f))) { + f = stdin; + if (! isatty (fileno (f))) + f = NULL; + } + } + + return (_ecolor (f, color)); +} + +#define LASTCMD(_cmd) \ + unsetenv ("EINFO_LASTCMD"); \ + setenv ("EINFO_LASTCMD", _cmd, 1); + +#define EINFOVN(_file, _color) \ +{ \ + char *_e = getenv ("EINFO_LASTCMD"); \ + if (_e && ! colour_terminal (_file) && strcmp (_e, "ewarn") != 0 && \ + _e[strlen (_e) - 1] == 'n') \ + fprintf (_file, "\n"); \ + if (_eprefix) \ + fprintf (_file, "%s%s%s|", _ecolor (_file, _color), _eprefix, _ecolor (_file, ECOLOR_NORMAL)); \ + fprintf (_file, " %s*%s ", _ecolor (_file, _color), _ecolor (_file, ECOLOR_NORMAL)); \ + retval += _eindent (_file); \ +{ \ + va_list _ap; \ + va_copy (_ap, ap); \ + retval += vfprintf (_file, fmt, _ap) + 3; \ + va_end (_ap); \ +} \ + if (colour_terminal (_file)) \ + fprintf (_file, "%s", flush); \ +} + +static int _einfovn (const char *__EINFO_RESTRICT fmt, va_list ap) +{ + int retval = 0; + + EINFOVN (stdout, ECOLOR_GOOD); + return (retval); +} + +static int _ewarnvn (const char *__EINFO_RESTRICT fmt, va_list ap) +{ + int retval = 0; + + EINFOVN (stdout, ECOLOR_WARN); + return (retval); +} + +static int _eerrorvn (const char *__EINFO_RESTRICT fmt, va_list ap) +{ + int retval = 0; + + EINFOVN (stderr, ECOLOR_BAD); + return (retval); +} + +int einfon (const char *__EINFO_RESTRICT fmt, ...) +{ + int retval; + va_list ap; + + if (! fmt || is_quiet ()) + return (0); + + va_start (ap, fmt); + retval = _einfovn (fmt, ap); + va_end (ap); + + LASTCMD ("einfon"); + + return (retval); +} +hidden_def(einfon) + +int ewarnn (const char *__EINFO_RESTRICT fmt, ...) +{ + int retval; + va_list ap; + + if (! fmt || is_quiet ()) + return (0); + + va_start (ap, fmt); + retval = _ewarnvn (fmt, ap); + va_end (ap); + + LASTCMD ("ewarnn"); + + return (retval); +} +hidden_def(ewarnn) + +int eerrorn (const char *__EINFO_RESTRICT fmt, ...) +{ + int retval; + va_list ap; + + va_start (ap, fmt); + retval = _eerrorvn (fmt, ap); + va_end (ap); + + LASTCMD ("errorn"); + + return (retval); +} +hidden_def(eerrorn) + +int einfo (const char *__EINFO_RESTRICT fmt, ...) +{ + int retval; + va_list ap; + + if (! fmt || is_quiet()) + return (0); + + va_start (ap, fmt); + retval = _einfovn (fmt, ap); + retval += printf ("\n"); + va_end (ap); + + LASTCMD ("einfo"); + + return (retval); +} +hidden_def(einfo) + +int ewarn (const char *__EINFO_RESTRICT fmt, ...) +{ + int retval; + va_list ap; + + if (! fmt || is_quiet ()) + return (0); + + va_start (ap, fmt); + elogv (LOG_WARNING, fmt, ap); + retval = _ewarnvn (fmt, ap); + retval += printf ("\n"); + va_end (ap); + + LASTCMD ("ewarn"); + + return (retval); +} +hidden_def(ewarn) + +void ewarnx (const char *__EINFO_RESTRICT fmt, ...) +{ + int retval; + va_list ap; + + if (fmt && ! is_quiet ()) { + va_start (ap, fmt); + elogv (LOG_WARNING, fmt, ap); + retval = _ewarnvn (fmt, ap); + va_end (ap); + retval += printf ("\n"); + } + exit (EXIT_FAILURE); +} +hidden_def(ewarnx) + +int eerror (const char *__EINFO_RESTRICT fmt, ...) +{ + int retval; + va_list ap; + + if (! fmt) + return (0); + + va_start (ap, fmt); + elogv (LOG_ERR, fmt, ap); + retval = _eerrorvn (fmt, ap); + va_end (ap); + retval += fprintf (stderr, "\n"); + + LASTCMD ("eerror"); + + return (retval); +} +hidden_def(eerror) + +void eerrorx (const char *__EINFO_RESTRICT fmt, ...) +{ + va_list ap; + + if (fmt) { + va_start (ap, fmt); + elogv (LOG_ERR, fmt, ap); + _eerrorvn (fmt, ap); + va_end (ap); + fprintf (stderr, "\n"); + } + + exit (EXIT_FAILURE); +} +hidden_def(eerrorx) + +int ebegin (const char *__EINFO_RESTRICT fmt, ...) +{ + int retval; + va_list ap; + + if (! fmt || is_quiet ()) + return (0); + + va_start (ap, fmt); + retval = _einfovn (fmt, ap); + va_end (ap); + retval += printf (" ..."); + if (colour_terminal (stdout)) + retval += printf ("\n"); + + LASTCMD ("ebegin"); + + return (retval); +} +hidden_def(ebegin) + +static void _eend (FILE * __EINFO_RESTRICT fp, int col, einfo_color_t color, + const char *msg) +{ + int i; + int cols; + + if (! msg) + return; + + cols = get_term_columns (fp) - (strlen (msg) + 3); + + /* cons25 is special - we need to remove one char, otherwise things + * do not align properly at all. */ + if (! term) { + term = getenv ("TERM"); + if (term && strcmp (term, "cons25") == 0) + term_is_cons25 = true; + else + term_is_cons25 = false; + } + if (term_is_cons25) + cols--; + + if (cols > 0 && colour_terminal (fp)) { + fprintf (fp, "%s%s %s[%s%s%s]%s\n", up, tgoto (goto_column, 0, cols), + ecolor (ECOLOR_BRACKET), ecolor (color), msg, + ecolor (ECOLOR_BRACKET), ecolor (ECOLOR_NORMAL)); + } else { + if (col > 0) + for (i = 0; i < cols - col; i++) + fprintf (fp, " "); + fprintf (fp, " [%s]\n", msg); + } +} + +static int _do_eend (const char *cmd, int retval, const char *__EINFO_RESTRICT fmt, va_list ap) +{ + int col = 0; + FILE *fp = stdout; + va_list apc; + + if (fmt && retval != 0) { + va_copy (apc, ap); + if (strcmp (cmd, "ewend") == 0) { + col = _ewarnvn (fmt, apc); + fp = stdout; + } else { + col = _eerrorvn (fmt, apc); + fp = stderr; + } + col += fprintf (fp, "\n"); + va_end (apc); + } + + _eend (fp, col, + retval == 0 ? ECOLOR_GOOD : ECOLOR_BAD, + retval == 0 ? OK : NOT_OK); + return (retval); +} + +int eend (int retval, const char *__EINFO_RESTRICT fmt, ...) +{ + va_list ap; + + if (is_quiet ()) + return (retval); + + va_start (ap, fmt); + _do_eend ("eend", retval, fmt, ap); + va_end (ap); + + LASTCMD ("eend"); + + return (retval); +} +hidden_def(eend) + +int ewend (int retval, const char *__EINFO_RESTRICT fmt, ...) +{ + va_list ap; + + if (is_quiet ()) + return (retval); + + va_start (ap, fmt); + _do_eend ("ewend", retval, fmt, ap); + va_end (ap); + + LASTCMD ("ewend"); + + return (retval); +} +hidden_def(ewend) + +void ebracket (int col, einfo_color_t color, const char *msg) +{ + _eend (stdout, col, color, msg); +} +hidden_def(ebracket) + +void eindent (void) +{ + char *env = getenv ("EINFO_INDENT"); + int amount = 0; + char num[10]; + + if (env) { + errno = 0; + amount = strtol (env, NULL, 0); + if (errno != 0) + amount = 0; + } + + amount += INDENT_WIDTH; + if (amount > INDENT_MAX) + amount = INDENT_MAX; + + snprintf (num, 10, "%08d", amount); + setenv ("EINFO_INDENT", num, 1); +} +hidden_def(eindent) + +void eoutdent (void) +{ + char *env = getenv ("EINFO_INDENT"); + int amount = 0; + char num[10]; + + if (! env) + return; + + errno = 0; + amount = strtol (env, NULL, 0); + if (errno != 0) + amount = 0; + else + amount -= INDENT_WIDTH; + + if (amount <= 0) + unsetenv ("EINFO_EINDENT"); + else { + snprintf (num, 10, "%08d", amount); + setenv ("EINFO_EINDENT", num, 1); + } +} +hidden_def(eoutdent) + +int einfovn (const char *__EINFO_RESTRICT fmt, ...) +{ + int retval; + va_list ap; + + if (! fmt || ! is_verbose ()) + return (0); + + va_start (ap, fmt); + retval = _einfovn (fmt, ap); + va_end (ap); + + LASTCMD ("einfovn"); + + return (retval); +} +hidden_def(einfovn) + +int ewarnvn (const char *__EINFO_RESTRICT fmt, ...) +{ + int retval; + va_list ap; + + if (! fmt || ! is_verbose ()) + return (0); + + va_start (ap, fmt); + retval = _ewarnvn (fmt, ap); + va_end (ap); + + LASTCMD ("ewarnvn"); + + return (retval); +} +hidden_def(ewarnvn) + +int einfov (const char *__EINFO_RESTRICT fmt, ...) +{ + int retval; + va_list ap; + + if (! fmt || ! is_verbose ()) + return (0); + + va_start (ap, fmt); + retval = _einfovn (fmt, ap); + retval += printf ("\n"); + va_end (ap); + + LASTCMD ("einfov"); + + return (retval); +} +hidden_def(einfov) + +int ewarnv (const char *__EINFO_RESTRICT fmt, ...) +{ + int retval; + va_list ap; + + if (! fmt || ! is_verbose ()) + return (0); + + va_start (ap, fmt); + retval = _ewarnvn (fmt, ap); + retval += printf ("\n"); + va_end (ap); + + LASTCMD ("ewarnv"); + + return (retval); +} +hidden_def(ewarnv) + +int ebeginv (const char *__EINFO_RESTRICT fmt, ...) +{ + int retval; + va_list ap; + + if (! fmt || ! is_verbose ()) + return (0); + + va_start (ap, fmt); + retval = _einfovn (fmt, ap); + retval += printf (" ..."); + if (colour_terminal (stdout)) + retval += printf ("\n"); + va_end (ap); + + LASTCMD ("ebeginv"); + + return (retval); +} +hidden_def(ebeginv) + +int eendv (int retval, const char *__EINFO_RESTRICT fmt, ...) +{ + va_list ap; + + if (! is_verbose ()) + return (0); + + va_start (ap, fmt); + _do_eend ("eendv", retval, fmt, ap); + va_end (ap); + + LASTCMD ("eendv"); + + return (retval); +} +hidden_def(eendv) + +int ewendv (int retval, const char *__EINFO_RESTRICT fmt, ...) +{ + va_list ap; + + if (! is_verbose ()) + return (0); + + va_start (ap, fmt); + _do_eend ("ewendv", retval, fmt, ap); + va_end (ap); + + LASTCMD ("ewendv"); + + return (retval); +} +hidden_def(ewendv) + +void eindentv (void) +{ + if (is_verbose ()) + eindent (); +} +hidden_def(eindentv) + +void eoutdentv (void) +{ + if (is_verbose ()) + eoutdent (); +} +hidden_def(eoutdentv) diff --git a/src/librc-daemon.c b/src/librc-daemon.c deleted file mode 100644 index fe9509de..00000000 --- a/src/librc-daemon.c +++ /dev/null @@ -1,574 +0,0 @@ -/* - librc-daemon - Finds PID for given daemon criteria - */ - -/* - * 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 "librc.h" - -#if defined(__linux__) -static bool pid_is_cmd (pid_t pid, const char *cmd) -{ - char buffer[32]; - FILE *fp; - int c; - - snprintf(buffer, sizeof (buffer), "/proc/%d/stat", pid); - if ((fp = fopen (buffer, "r")) == NULL) - return (false); - - while ((c = getc (fp)) != EOF && c != '(') - ; - - if (c != '(') { - fclose(fp); - return (false); - } - - while ((c = getc (fp)) != EOF && c == *cmd) - cmd++; - - fclose (fp); - - return ((c == ')' && *cmd == '\0') ? true : false); -} - -static bool pid_is_exec (pid_t pid, const char *exec) -{ - char cmdline[32]; - char buffer[PATH_MAX]; - char *p; - int fd = -1; - int r; - - snprintf (cmdline, sizeof (cmdline), "/proc/%u/exe", pid); - memset (buffer, 0, sizeof (buffer)); - if (readlink (cmdline, buffer, sizeof (buffer)) != -1) { - if (strcmp (exec, buffer) == 0) - return (true); - - /* We should cater for deleted binaries too */ - if (strlen (buffer) > 10) { - p = buffer + (strlen (buffer) - 10); - if (strcmp (p, " (deleted)") == 0) { - *p = 0; - if (strcmp (buffer, exec) == 0) - return (true); - } - } - } - - snprintf (cmdline, sizeof (cmdline), "/proc/%u/cmdline", pid); - if ((fd = open (cmdline, O_RDONLY)) < 0) - return (false); - - r = read(fd, buffer, sizeof (buffer)); - close (fd); - - if (r == -1) - return 0; - - buffer[r] = 0; - return (strcmp (exec, buffer) == 0 ? true : false); -} - -pid_t *rc_find_pids (const char *exec, const char *cmd, - uid_t uid, pid_t pid) -{ - DIR *procdir; - struct dirent *entry; - int npids = 0; - pid_t p; - pid_t *pids = NULL; - pid_t *tmp = NULL; - char buffer[PATH_MAX]; - struct stat sb; - pid_t runscript_pid = 0; - char *pp; - - if ((procdir = opendir ("/proc")) == NULL) - return (NULL); - - /* - We never match RC_RUNSCRIPT_PID if present so we avoid the below - scenario - - /etc/init.d/ntpd stop does - start-stop-daemon --stop --name ntpd - catching /etc/init.d/ntpd stop - - nasty - */ - - if ((pp = getenv ("RC_RUNSCRIPT_PID"))) { - if (sscanf (pp, "%d", &runscript_pid) != 1) - runscript_pid = 0; - } - - while ((entry = readdir (procdir)) != NULL) { - if (sscanf (entry->d_name, "%d", &p) != 1) - continue; - - if (runscript_pid != 0 && runscript_pid == p) - continue; - - if (pid != 0 && pid != p) - continue; - - if (uid) { - snprintf (buffer, sizeof (buffer), "/proc/%d", p); - if (stat (buffer, &sb) != 0 || sb.st_uid != uid) - continue; - } - - if (cmd && ! pid_is_cmd (p, cmd)) - continue; - - if (exec && ! cmd && ! pid_is_exec (p, exec)) - continue; - - tmp = realloc (pids, sizeof (pid_t) * (npids + 2)); - if (! tmp) { - free (pids); - closedir (procdir); - errno = ENOMEM; - return (NULL); - } - pids = tmp; - - pids[npids] = p; - pids[npids + 1] = 0; - npids++; - } - closedir (procdir); - - return (pids); -} -librc_hidden_def(rc_find_pids) - -#elif BSD - -# if defined(__DragonFly__) || defined(__FreeBSD__) -# ifndef KERN_PROC_PROC -# define KERN_PROC_PROC KERN_PROC_ALL -# endif -# define _KINFO_PROC kinfo_proc -# define _KVM_GETARGV kvm_getargv -# define _GET_KINFO_UID(kp) (kp.ki_ruid) -# define _GET_KINFO_COMM(kp) (kp.ki_comm) -# define _GET_KINFO_PID(kp) (kp.ki_pid) -# else -# define _KVM_GETPROC2 -# define _KINFO_PROC kinfo_proc2 -# define _KVM_GETARGV kvm_getargv2 -# define _GET_KINFO_UID(kp) (kp.p_ruid) -# define _GET_KINFO_COMM(kp) (kp.p_comm) -# define _GET_KINFO_PID(kp) (kp.p_pid) -# endif - -pid_t *rc_find_pids (const char *exec, const char *cmd, - uid_t uid, pid_t pid) -{ - static kvm_t *kd = NULL; - char errbuf[_POSIX2_LINE_MAX]; - struct _KINFO_PROC *kp; - int i; - int processes = 0; - int argc = 0; - char **argv; - pid_t *pids = NULL; - pid_t *tmp; - int npids = 0; - - if ((kd = kvm_openfiles (NULL, NULL, NULL, O_RDONLY, errbuf)) == NULL) { - fprintf (stderr, "kvm_open: %s", errbuf); - return (NULL); - } - -#ifdef _KVM_GETPROC2 - kp = kvm_getproc2 (kd, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc2), - &processes); -#else - kp = kvm_getprocs (kd, KERN_PROC_PROC, 0, &processes); -#endif - for (i = 0; i < processes; i++) { - pid_t p = _GET_KINFO_PID (kp[i]); - if (pid != 0 && pid != p) - continue; - - if (uid != 0 && uid != _GET_KINFO_UID (kp[i])) - continue; - - if (cmd) { - if (! _GET_KINFO_COMM (kp[i]) || - strcmp (cmd, _GET_KINFO_COMM (kp[i])) != 0) - continue; - } - - if (exec && ! cmd) { - if ((argv = _KVM_GETARGV (kd, &kp[i], argc)) == NULL || ! *argv) - continue; - - if (strcmp (*argv, exec) != 0) - continue; - } - - tmp = realloc (pids, sizeof (pid_t) * (npids + 2)); - if (! tmp) { - free (pids); - kvm_close (kd); - errno = ENOMEM; - return (NULL); - } - pids = tmp; - - pids[npids] = p; - pids[npids + 1] = 0; - npids++; - } - kvm_close (kd); - - return (pids); -} -librc_hidden_def(rc_find_pids) - -#else -# error "Platform not supported!" -#endif - -static bool _match_daemon (const char *path, const char *file, - const char *mexec, const char *mname, - const char *mpidfile) -{ - char *buffer; - char *ffile = rc_strcatpaths (path, file, (char *) NULL); - FILE *fp; - int lc = 0; - int m = 0; - - if ((fp = fopen (ffile, "r")) == NULL) { - free (ffile); - return (false); - } - - if (! mname) - m += 10; - if (! mpidfile) - m += 100; - - buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); - memset (buffer, 0, RC_LINEBUFFER); - while ((fgets (buffer, RC_LINEBUFFER, fp))) { - int lb = strlen (buffer) - 1; - if (buffer[lb] == '\n') - buffer[lb] = 0; - - if (strcmp (buffer, mexec) == 0) - m += 1; - else if (mname && strcmp (buffer, mname) == 0) - m += 10; - else if (mpidfile && strcmp (buffer, mpidfile) == 0) - m += 100; - - if (m == 111) - break; - - lc++; - if (lc > 5) - break; - } - free (buffer); - fclose (fp); - free (ffile); - - return (m == 111 ? true : false); -} - -bool rc_service_daemon_set (const char *service, const char *exec, - const char *name, const char *pidfile, - bool started) -{ - char *dirpath; - char *file = NULL; - int i; - char *mexec; - char *mname; - char *mpidfile; - int nfiles = 0; - char *oldfile = NULL; - bool retval = false; - DIR *dp; - struct dirent *d; - - if (! exec && ! name && ! pidfile) { - errno = EINVAL; - return (false); - } - - dirpath = rc_strcatpaths (RC_SVCDIR, "daemons", - basename_c (service), (char *) NULL); - - if (exec) { - i = strlen (exec) + 6; - mexec = xmalloc (sizeof (char) * i); - snprintf (mexec, i, "exec=%s", exec); - } else - mexec = xstrdup ("exec="); - - if (name) { - i = strlen (name) + 6; - mname = xmalloc (sizeof (char) * i); - snprintf (mname, i, "name=%s", name); - } else - mname = xstrdup ("name="); - - if (pidfile) { - i = strlen (pidfile) + 9; - mpidfile = xmalloc (sizeof (char) * i); - snprintf (mpidfile, i, "pidfile=%s", pidfile); - } else - mpidfile = xstrdup ("pidfile="); - - /* Regardless, erase any existing daemon info */ - if ((dp = opendir (dirpath))) { - while ((d = readdir (dp))) { - if (d->d_name[0] == '.') - continue; - file = rc_strcatpaths (dirpath, d->d_name, (char *) NULL); - nfiles++; - - if (! oldfile) { - if (_match_daemon (dirpath, d->d_name, - mexec, mname, mpidfile)) - { - unlink (file); - oldfile = file; - nfiles--; - } - } else { - rename (file, oldfile); - free (oldfile); - oldfile = file; - } - } - free (file); - closedir (dp); - } - - /* Now store our daemon info */ - if (started) { - char buffer[10]; - FILE *fp; - - if (mkdir (dirpath, 0755) == 0 || errno == EEXIST) { - snprintf (buffer, sizeof (buffer), "%03d", nfiles + 1); - file = rc_strcatpaths (dirpath, buffer, (char *) NULL); - if ((fp = fopen (file, "w"))) { - fprintf (fp, "%s\n%s\n%s\n", mexec, mname, mpidfile); - fclose (fp); - retval = true; - } - free (file); - } - } else - retval = true; - - free (mexec); - free (mname); - free (mpidfile); - free (dirpath); - - return (retval); -} -librc_hidden_def(rc_service_daemon_set) - -bool rc_service_started_daemon (const char *service, const char *exec, - int indx) -{ - char *dirpath; - char *file; - int i; - char *mexec; - bool retval = false; - DIR *dp; - struct dirent *d; - - if (! service || ! exec) - return (false); - - dirpath = rc_strcatpaths (RC_SVCDIR, "daemons", basename_c (service), - (char *) NULL); - - i = strlen (exec) + 6; - mexec = xmalloc (sizeof (char) * i); - snprintf (mexec, i, "exec=%s", exec); - - if (indx > 0) { - int len = sizeof (char) * 10; - file = xmalloc (len); - snprintf (file, len, "%03d", indx); - retval = _match_daemon (dirpath, file, mexec, NULL, NULL); - free (file); - } else { - if ((dp = opendir (dirpath))) { - while ((d = readdir (dp))) { - if (d->d_name[0] == ',') - continue; - retval = _match_daemon (dirpath, d->d_name, mexec, NULL, NULL); - if (retval) - break; - } - closedir (dp); - } - } - - free (dirpath); - free (mexec); - return (retval); -} -librc_hidden_def(rc_service_started_daemon) - -bool rc_service_daemons_crashed (const char *service) -{ - char *dirpath; - DIR *dp; - struct dirent *d; - char *path; - FILE *fp; - char *buffer = NULL; - char *exec = NULL; - char *name = NULL; - char *pidfile = NULL; - pid_t pid = 0; - pid_t *pids = NULL; - char *p; - char *token; - bool retval = false; - - if (! service) - return (false); - - dirpath = rc_strcatpaths (RC_SVCDIR, "daemons", basename_c (service), - (char *) NULL); - - if (! (dp = opendir (dirpath))) { - free (dirpath); - return (false); - } - - buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); - memset (buffer, 0, RC_LINEBUFFER); - - while ((d = readdir (dp))) { - if (d->d_name[0] == '.') - continue; - - path = rc_strcatpaths (dirpath, d->d_name, (char *) NULL); - fp = fopen (path, "r"); - free (path); - if (! fp) - break; - - while ((fgets (buffer, RC_LINEBUFFER, fp))) { - int lb = strlen (buffer) - 1; - if (buffer[lb] == '\n') - buffer[lb] = 0; - - p = buffer; - if ((token = strsep (&p, "=")) == NULL || ! p) - continue; - - if (strlen (p) == 0) - continue; - - if (strcmp (token, "exec") == 0) { - if (exec) - free (exec); - exec = xstrdup (p); - } else if (strcmp (token, "name") == 0) { - if (name) - free (name); - name = xstrdup (p); - } else if (strcmp (token, "pidfile") == 0) { - if (pidfile) - free (pidfile); - pidfile = xstrdup (p); - } - } - fclose (fp); - - pid = 0; - if (pidfile) { - if (! exists (pidfile)) { - retval = true; - break; - } - - if ((fp = fopen (pidfile, "r")) == NULL) { - retval = true; - break; - } - - if (fscanf (fp, "%d", &pid) != 1) { - fclose (fp); - retval = true; - break; - } - - fclose (fp); - free (pidfile); - pidfile = NULL; - - /* We have the pid, so no need to match on name */ - free (exec); - exec = NULL; - free (name); - name = NULL; - } - - if ((pids = rc_find_pids (exec, name, 0, pid)) == NULL) { - retval = true; - break; - } - free (pids); - - free (exec); - exec = NULL; - free (name); - name = NULL; - } - - free (buffer); - free (exec); - free (name); - free (dirpath); - closedir (dp); - - return (retval); -} -librc_hidden_def(rc_service_daemons_crashed) diff --git a/src/librc-depend.c b/src/librc-depend.c deleted file mode 100644 index 902d012f..00000000 --- a/src/librc-depend.c +++ /dev/null @@ -1,979 +0,0 @@ -/* - librc-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 "librc.h" - -#define GENDEP RC_LIBDIR "/sh/gendepends.sh" - -#define RC_DEPCONFIG RC_SVCDIR "/depconfig" - -static const char *bootlevel = NULL; - -/* We use this so we can pass our char array through many functions */ -struct lhead -{ - char **list; -}; - -static char *get_shell_value (char *string) -{ - char *p = string; - char *e; - - if (! string) - return (NULL); - - if (*p == '\'') - p++; - - e = p + strlen (p) - 1; - if (*e == '\n') - *e-- = 0; - if (*e == '\'') - *e-- = 0; - - if (*p != 0) - return p; - - return (NULL); -} - -void rc_deptree_free (rc_depinfo_t *deptree) -{ - rc_depinfo_t *di = deptree; - while (di) - { - rc_depinfo_t *dip = di->next; - rc_deptype_t *dt = di->depends; - free (di->service); - while (dt) - { - rc_deptype_t *dtp = dt->next; - free (dt->type); - rc_strlist_free (dt->services); - free (dt); - dt = dtp; - } - free (di); - di = dip; - } -} -librc_hidden_def(rc_deptree_free) - -static rc_depinfo_t *get_depinfo (const rc_depinfo_t *deptree, - const char *service) -{ - const rc_depinfo_t *di; - - if (! deptree || ! service) - return (NULL); - - for (di = deptree; di; di = di->next) - if (strcmp (di->service, service) == 0) - return ((rc_depinfo_t *)di); - - return (NULL); -} - -static rc_deptype_t *get_deptype (const rc_depinfo_t *depinfo, - const char *type) -{ - rc_deptype_t *dt; - - if (! depinfo || !type) - return (NULL); - - for (dt = depinfo->depends; dt; dt = dt->next) - if (strcmp (dt->type, type) == 0) - return (dt); - - return (NULL); -} - -rc_depinfo_t *rc_deptree_load (void) -{ - FILE *fp; - rc_depinfo_t *deptree = NULL; - rc_depinfo_t *depinfo = NULL; - rc_deptype_t *deptype = NULL; - char buffer [RC_LINEBUFFER]; - char *type; - char *p; - char *e; - int i; - - if (! (fp = fopen (RC_DEPTREE, "r"))) - return (NULL); - - while (fgets (buffer, RC_LINEBUFFER, fp)) - { - p = buffer; - e = strsep (&p, "_"); - if (! e || strcmp (e, "depinfo") != 0) - continue; - - e = strsep (&p, "_"); - if (! e || sscanf (e, "%d", &i) != 1) - continue; - - if (! (type = strsep (&p, "_="))) - continue; - - if (strcmp (type, "service") == 0) - { - /* Sanity */ - e = get_shell_value (p); - if (! e || strlen (e) == 0) - continue; - - if (! deptree) - { - deptree = xmalloc (sizeof (rc_depinfo_t)); - depinfo = deptree; - } - else - { - depinfo->next = xmalloc (sizeof (rc_depinfo_t)); - depinfo = depinfo->next; - } - memset (depinfo, 0, sizeof (rc_depinfo_t)); - depinfo->service = xstrdup (e); - deptype = NULL; - continue; - } - - e = strsep (&p, "="); - if (! e || sscanf (e, "%d", &i) != 1) - continue; - - /* Sanity */ - e = get_shell_value (p); - if (! e || strlen (e) == 0) - continue; - - if (! deptype) - { - depinfo->depends = xmalloc (sizeof (rc_deptype_t)); - deptype = depinfo->depends; - memset (deptype, 0, sizeof (rc_deptype_t)); - } - else - if (strcmp (deptype->type, type) != 0) - { - deptype->next = xmalloc (sizeof (rc_deptype_t)); - deptype = deptype->next; - memset (deptype, 0, sizeof (rc_deptype_t)); - } - - if (! deptype->type) - deptype->type = xstrdup (type); - - rc_strlist_addsort (&deptype->services, e); - } - fclose (fp); - - return (deptree); -} -librc_hidden_def(rc_deptree_load) - -static bool valid_service (const char *runlevel, const char *service) -{ - rc_service_state_t state = rc_service_state (service); - - return ((strcmp (runlevel, bootlevel) != 0 && - rc_service_in_runlevel (service, bootlevel)) || - rc_service_in_runlevel (service, runlevel) || - state & RC_SERVICE_COLDPLUGGED || - state & RC_SERVICE_STARTED); -} - -static bool get_provided1 (const char *runlevel, struct lhead *providers, - rc_deptype_t *deptype, - const char *level, bool coldplugged, - rc_service_state_t state) -{ - char *service; - int i; - bool retval = false; - - STRLIST_FOREACH (deptype->services, service, i) - { - bool ok = true; - rc_service_state_t s = rc_service_state (service); - if (level) - ok = rc_service_in_runlevel (service, level); - else if (coldplugged) - ok = (s & RC_SERVICE_COLDPLUGGED && - ! rc_service_in_runlevel (service, runlevel) && - ! rc_service_in_runlevel (service, bootlevel)); - - if (! ok) - continue; - - switch (state) { - case RC_SERVICE_STARTED: - ok = (s & RC_SERVICE_STARTED); - break; - case RC_SERVICE_INACTIVE: - case RC_SERVICE_STARTING: - case RC_SERVICE_STOPPING: - ok = (s & RC_SERVICE_STARTING || - s & RC_SERVICE_STOPPING || - s & RC_SERVICE_INACTIVE); - break; - default: - break; - } - - if (! ok) - continue; - - retval = true; - rc_strlist_add (&providers->list, service); - } - - return (retval); -} - -/* Work out if a service is provided by another service. - For example metalog provides logger. - We need to be able to handle syslogd providing logger too. - We do this by checking whats running, then what's starting/stopping, - then what's run in the runlevels and finally alphabetical order. - - If there are any bugs in rc-depend, they will probably be here as - provided dependancy can change depending on runlevel state. - */ -static char **get_provided (const rc_depinfo_t *deptree, - const rc_depinfo_t *depinfo, - const char *runlevel, int options) -{ - rc_deptype_t *dt; - struct lhead providers; - char *service; - int i; - - if (! deptree || ! depinfo) - return (NULL); - if (rc_service_exists (depinfo->service)) - return (NULL); - - dt = get_deptype (depinfo, "providedby"); - if (! dt) - return (NULL); - - memset (&providers, 0, sizeof (struct lhead)); - /* If we are stopping then all depends are true, regardless of state. - This is especially true for net services as they could force a restart - of the local dns resolver which may depend on net. */ - if (options & RC_DEP_STOP) - { - STRLIST_FOREACH (dt->services, service, i) - rc_strlist_add (&providers.list, service); - - return (providers.list); - } - - /* If we're strict, then only use what we have in our runlevel - * and bootlevel */ - if (options & RC_DEP_STRICT) - { - STRLIST_FOREACH (dt->services, service, i) - if (rc_service_in_runlevel (service, runlevel) || - rc_service_in_runlevel (service, bootlevel)) - rc_strlist_add (&providers.list, service); - - if (providers.list) - return (providers.list); - } - - /* OK, we're not strict or there were no services in our runlevel. - This is now where the logic gets a little fuzzy :) - If there is >1 running service then we return NULL. - We do this so we don't hang around waiting for inactive services and - our need has already been satisfied as it's not strict. - We apply this to our runlevel, coldplugged services, then bootlevel - and finally any running.*/ -#define DO \ - if (providers.list && providers.list[0] && providers.list[1]) \ - { \ - rc_strlist_free (providers.list); \ - return (NULL); \ - } \ - else if (providers.list) \ - return providers.list; \ - - /* Anything in the runlevel has to come first */ - if (get_provided1 (runlevel, &providers, dt, runlevel, false, RC_SERVICE_STARTED)) - { DO } - if (get_provided1 (runlevel, &providers, dt, runlevel, false, RC_SERVICE_STARTING)) - return (providers.list); - if (get_provided1 (runlevel, &providers, dt, runlevel, false, RC_SERVICE_STOPPED)) - return (providers.list); - - /* Check coldplugged services */ - if (get_provided1 (runlevel, &providers, dt, NULL, true, RC_SERVICE_STARTED)) - { DO } - if (get_provided1 (runlevel, &providers, dt, NULL, true, RC_SERVICE_STARTING)) - return (providers.list); - - /* Check bootlevel if we're not in it */ - if (bootlevel && strcmp (runlevel, bootlevel) != 0) - { - if (get_provided1 (runlevel, &providers, dt, bootlevel, false, RC_SERVICE_STARTED)) - { DO } - if (get_provided1 (runlevel, &providers, dt, bootlevel, false, RC_SERVICE_STARTING)) - return (providers.list); - } - - /* Check coldplugged services */ - if (get_provided1 (runlevel, &providers, dt, NULL, true, RC_SERVICE_STOPPED)) - { DO } - - /* Check manually started */ - if (get_provided1 (runlevel, &providers, dt, NULL, false, RC_SERVICE_STARTED)) - { DO } - if (get_provided1 (runlevel, &providers, dt, NULL, false, RC_SERVICE_STARTING)) - return (providers.list); - - /* Nothing started then. OK, lets get the stopped services */ - if (get_provided1 (runlevel, &providers, dt, runlevel, false, RC_SERVICE_STOPPED)) - return (providers.list); - - if (bootlevel && (strcmp (runlevel, bootlevel) != 0) - && (get_provided1 (runlevel, &providers, dt, bootlevel, false, RC_SERVICE_STOPPED))) - return (providers.list); - - /* Still nothing? OK, list all services */ - STRLIST_FOREACH (dt->services, service, i) - rc_strlist_add (&providers.list, service); - - return (providers.list); -} - -static void visit_service (const rc_depinfo_t *deptree, - const char * const *types, - struct lhead *sorted, struct lhead *visited, - const rc_depinfo_t *depinfo, - const char *runlevel, int options) -{ - int i, j, k; - char *lp; - const char *item; - char *service; - rc_depinfo_t *di; - rc_deptype_t *dt; - char **provides; - char *svcname; - - if (! deptree || !sorted || !visited || !depinfo) - return; - - /* Check if we have already visited this service or not */ - STRLIST_FOREACH (visited->list, item, i) - if (strcmp (item, depinfo->service) == 0) - return; - - /* Add ourselves as a visited service */ - rc_strlist_add (&visited->list, depinfo->service); - - STRLIST_FOREACH (types, item, i) - { - if ((dt = get_deptype (depinfo, item))) - { - STRLIST_FOREACH (dt->services, service, j) - { - if (! options & RC_DEP_TRACE || strcmp (item, "iprovide") == 0) - { - rc_strlist_add (&sorted->list, service); - continue; - } - - di = get_depinfo (deptree, service); - if ((provides = get_provided (deptree, di, runlevel, options))) - { - STRLIST_FOREACH (provides, lp, k) - { - di = get_depinfo (deptree, lp); - if (di && (strcmp (item, "ineed") == 0 || - strcmp (item, "needsme") == 0 || - valid_service (runlevel, di->service))) - visit_service (deptree, types, sorted, visited, di, - runlevel, options | RC_DEP_TRACE); - } - rc_strlist_free (provides); - } - else - if (di && (strcmp (item, "ineed") == 0 || - strcmp (item, "needsme") == 0 || - valid_service (runlevel, service))) - visit_service (deptree, types, sorted, visited, di, - runlevel, options | RC_DEP_TRACE); - } - } - } - - /* Now visit the stuff we provide for */ - if (options & RC_DEP_TRACE && - (dt = get_deptype (depinfo, "iprovide"))) - { - STRLIST_FOREACH (dt->services, service, i) - { - if ((di = get_depinfo (deptree, service))) - if ((provides = get_provided (deptree, di, runlevel, options))) - { - STRLIST_FOREACH (provides, lp, j) - if (strcmp (lp, depinfo->service) == 0) - { - visit_service (deptree, types, sorted, visited, di, - runlevel, options | RC_DEP_TRACE); - break; - } - rc_strlist_free (provides); - } - } - } - - /* We've visited everything we need, so add ourselves unless we - are also the service calling us or we are provided by something */ - svcname = getenv("SVCNAME"); - if (! svcname || strcmp (svcname, depinfo->service) != 0) - if (! get_deptype (depinfo, "providedby")) - rc_strlist_add (&sorted->list, depinfo->service); -} - -char **rc_deptree_depend (const rc_depinfo_t *deptree, - const char *service, const char *type) -{ - rc_depinfo_t *di; - rc_deptype_t *dt; - char **svcs = NULL; - int i; - char *svc; - - if (! (di = get_depinfo (deptree, service)) || - ! (dt = get_deptype (di, type))) - { - errno = ENOENT; - return (NULL); - } - - /* For consistency, we copy the array */ - STRLIST_FOREACH (dt->services, svc, i) - rc_strlist_add (&svcs, svc); - - return (svcs); -} -librc_hidden_def(rc_deptree_depend) - -char **rc_deptree_depends (const rc_depinfo_t *deptree, - const char *const *types, - const char *const *services, - const char *runlevel, int options) -{ - struct lhead sorted; - struct lhead visited; - rc_depinfo_t *di; - const char *service; - int i; - - if (! deptree || ! services) - return (NULL); - - memset (&sorted, 0, sizeof (struct lhead)); - memset (&visited, 0, sizeof (struct lhead)); - - bootlevel = getenv ("RC_BOOTLEVEL"); - if (! bootlevel) - bootlevel = RC_LEVEL_BOOT; - - STRLIST_FOREACH (services, service, i) - { - if (! (di = get_depinfo (deptree, service))) { - errno = ENOENT; - continue; - } - if (types) - visit_service (deptree, types, &sorted, &visited, - di, runlevel, options); - } - - rc_strlist_free (visited.list); - return (sorted.list); -} -librc_hidden_def(rc_deptree_depends) - -static const char * const order_types[] = { "ineed", "iuse", "iafter", NULL }; -char **rc_deptree_order (const rc_depinfo_t *deptree, const char *runlevel, - int options) -{ - char **list = NULL; - char **services = NULL; - bool reverse = false; - char **tmp = NULL; - - if (! runlevel) - return (NULL); - - bootlevel = getenv ("RC_BOOTLEVEL"); - if (! bootlevel) - bootlevel = RC_LEVEL_BOOT; - - /* When shutting down, list all running services */ - if (strcmp (runlevel, RC_LEVEL_SINGLE) == 0 || - strcmp (runlevel, RC_LEVEL_SHUTDOWN) == 0 || - strcmp (runlevel, RC_LEVEL_REBOOT) == 0) - { - list = rc_services_in_state (RC_SERVICE_STARTED); - - tmp = rc_services_in_state (RC_SERVICE_INACTIVE); - rc_strlist_join (&list, tmp); - rc_strlist_free (tmp); - - tmp = rc_services_in_state (RC_SERVICE_STARTING); - rc_strlist_join (&list, tmp); - rc_strlist_free (tmp); - reverse = true; - } else { - list = rc_services_in_runlevel (runlevel); - - /* Add coldplugged services */ - tmp = rc_services_in_state (RC_SERVICE_COLDPLUGGED); - rc_strlist_join (&list, tmp); - rc_strlist_free (tmp); - - /* If we're not the boot runlevel then add that too */ - if (strcmp (runlevel, bootlevel) != 0) { - tmp = rc_services_in_runlevel (bootlevel); - rc_strlist_join (&list, tmp); - rc_strlist_free (tmp); - } - } - - /* Now we have our lists, we need to pull in any dependencies - and order them */ - services = rc_deptree_depends (deptree, order_types, (const char **) list, - runlevel, - RC_DEP_STRICT | RC_DEP_TRACE | options); - rc_strlist_free (list); - - if (reverse) - rc_strlist_reverse (services); - - return (services); -} -librc_hidden_def(rc_deptree_order) - -static bool is_newer_than (const char *file, const char *target) -{ - struct stat buf; - time_t mtime; - bool newer = true; - DIR *dp; - struct dirent *d; - char *path; - - if (stat (file, &buf) != 0 || buf.st_size == 0) - return (false); - mtime = buf.st_mtime; - - /* Of course we are newever than targets that don't exist - Such as broken symlinks */ - if (stat (target, &buf) != 0) - return (true); - - if (mtime < buf.st_mtime) - return (false); - - if (! (dp = opendir (target))) - return (true); - - while ((d = readdir (dp))) { - if (d->d_name[0] == '.') - continue; - - path = rc_strcatpaths (target, d->d_name, (char *) NULL); - newer = is_newer_than (file, path); - free (path); - if (! newer) - break; - } - closedir (dp); - - return (newer); -} - -typedef struct deppair -{ - const char *depend; - const char *addto; -} deppair_t; - -static const deppair_t deppairs[] = { - { "ineed", "needsme" }, - { "iuse", "usesme" }, - { "iafter", "ibefore" }, - { "ibefore", "iafter" }, - { "iprovide", "providedby" }, - { NULL, NULL } -}; - -static const char *depdirs[] = -{ - RC_SVCDIR "/starting", - RC_SVCDIR "/started", - RC_SVCDIR "/stopping", - RC_SVCDIR "/inactive", - RC_SVCDIR "/wasinactive", - RC_SVCDIR "/failed", - RC_SVCDIR "/coldplugged", - RC_SVCDIR "/daemons", - RC_SVCDIR "/options", - RC_SVCDIR "/exclusive", - RC_SVCDIR "/scheduled", - NULL -}; - -bool rc_deptree_update_needed (void) -{ - bool newer = false; - char **config; - char *service; - int i; - - /* Create base directories if needed */ - for (i = 0; depdirs[i]; i++) - if (mkdir (depdirs[i], 0755) != 0 && errno != EEXIST) - fprintf (stderr, "mkdir `%s': %s\n", depdirs[i], strerror (errno)); - - /* Quick test to see if anything we use has changed */ - if (! is_newer_than (RC_DEPTREE, RC_INITDIR) || - ! is_newer_than (RC_DEPTREE, RC_CONFDIR) || - ! is_newer_than (RC_DEPTREE, RC_INITDIR_LOCAL) || - ! is_newer_than (RC_DEPTREE, RC_CONFDIR_LOCAL) || - ! is_newer_than (RC_DEPTREE, "/etc/rc.conf")) - return (true); - - /* Some init scripts dependencies change depending on config files - * outside of baselayout, like syslog-ng, so we check those too. */ - config = rc_config_list (RC_DEPCONFIG); - STRLIST_FOREACH (config, service, i) { - if (! is_newer_than (RC_DEPTREE, service)) { - newer = true; - break; - } - } - rc_strlist_free (config); - - return (newer); -} -librc_hidden_def(rc_deptree_update_needed) - -/* This is a 5 phase operation - Phase 1 is a shell script which loads each init script and config in turn - and echos their dependency info to stdout - Phase 2 takes that and populates a depinfo object with that data - Phase 3 adds any provided services to the depinfo object - Phase 4 scans that depinfo object and puts in backlinks - Phase 5 saves the depinfo object to disk - */ -bool rc_deptree_update (void) -{ - char *depends; - char *service; - char *type; - char *depend; - char **config = NULL; - int retval = true; - FILE *fp; - rc_depinfo_t *deptree; - rc_depinfo_t *depinfo; - rc_depinfo_t *di; - rc_depinfo_t *last_depinfo = NULL; - rc_deptype_t *deptype = NULL; - rc_deptype_t *dt; - rc_deptype_t *last_deptype = NULL; - char *buffer = NULL; - int len; - int i; - int j; - int k; - bool already_added; - - /* Some init scripts need RC_LIBDIR to source stuff - Ideally we should be setting our full env instead */ - if (! getenv ("RC_LIBDIR")) - setenv ("RC_LIBDIR", RC_LIBDIR, 0); - - /* Phase 1 */ - if (! (fp = popen (GENDEP, "r"))) - return (false); - - deptree = xmalloc (sizeof (rc_depinfo_t)); - memset (deptree, 0, sizeof (rc_depinfo_t)); - buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); - memset (buffer, 0, RC_LINEBUFFER); - - /* Phase 2 */ - while (fgets (buffer, RC_LINEBUFFER, fp)) - { - /* Trim the newline */ - if (buffer[strlen (buffer) - 1] == '\n') - buffer[strlen(buffer) -1] = 0; - - depends = buffer; - service = strsep (&depends, " "); - if (! service) - continue; - type = strsep (&depends, " "); - - for (depinfo = deptree; depinfo; depinfo = depinfo->next) - { - last_depinfo = depinfo; - if (depinfo->service && strcmp (depinfo->service, service) == 0) - break; - } - - if (! depinfo) - { - if (! last_depinfo->service) - depinfo = last_depinfo; - else - { - last_depinfo->next = xmalloc (sizeof (rc_depinfo_t)); - depinfo = last_depinfo->next; - } - memset (depinfo, 0, sizeof (rc_depinfo_t)); - depinfo->service = xstrdup (service); - } - - /* We may not have any depends */ - if (! type || ! depends) - continue; - - /* Get the type */ - if (strcmp (type, "config") != 0) { - last_deptype = NULL; - for (deptype = depinfo->depends; deptype; deptype = deptype->next) - { - last_deptype = deptype; - if (strcmp (deptype->type, type) == 0) - break; - } - - if (! deptype) - { - if (! last_deptype) - { - depinfo->depends = xmalloc (sizeof (rc_deptype_t)); - deptype = depinfo->depends; - } - else - { - last_deptype->next = xmalloc (sizeof (rc_deptype_t)); - deptype = last_deptype->next; - } - memset (deptype, 0, sizeof (rc_deptype_t)); - deptype->type = xstrdup (type); - } - } - - /* Now add each depend to our type. - We do this individually so we handle multiple spaces gracefully */ - while ((depend = strsep (&depends, " "))) - { - if (depend[0] == 0) - continue; - - if (strcmp (type, "config") == 0) { - rc_strlist_addsort (&config, depend); - continue; - } - - /* .sh files are not init scripts */ - len = strlen (depend); - if (len > 2 && - depend[len - 3] == '.' && - depend[len - 2] == 's' && - depend[len - 1] == 'h') - continue; - - rc_strlist_addsort (&deptype->services, depend); - - /* We need to allow `after *; before local;` to work. - * Conversely, we need to allow 'before *; after modules' also */ - /* If we're before something, remove us from the after list */ - if (strcmp (type, "ibefore") == 0) { - if ((dt = get_deptype (depinfo, "iafter"))) - rc_strlist_delete (&dt->services, depend); - } - /* If we're after something, remove us from the before list */ - if (strcmp (type, "iafter") == 0 || - strcmp (type, "ineed") == 0 || - strcmp (type, "iuse") == 0) { - if ((dt = get_deptype (depinfo, "ibefore"))) - rc_strlist_delete (&dt->services, depend); - } - } - } - pclose (fp); - free (buffer); - - /* Phase 3 - add our providors to the tree */ - for (depinfo = deptree; depinfo; depinfo = depinfo->next) - { - if ((deptype = get_deptype (depinfo, "iprovide"))) - STRLIST_FOREACH (deptype->services, service, i) - { - for (di = deptree; di; di = di->next) - { - last_depinfo = di; - if (strcmp (di->service, service) == 0) - break; - } - if (! di) - { - last_depinfo->next = xmalloc (sizeof (rc_depinfo_t)); - di = last_depinfo->next; - memset (di, 0, sizeof (rc_depinfo_t)); - di->service = xstrdup (service); - } - } - } - - /* Phase 4 - backreference our depends */ - for (depinfo = deptree; depinfo; depinfo = depinfo->next) - { - for (i = 0; deppairs[i].depend; i++) - { - deptype = get_deptype (depinfo, deppairs[i].depend); - if (! deptype) - continue; - - STRLIST_FOREACH (deptype->services, service, j) - { - di = get_depinfo (deptree, service); - if (! di) - { - if (strcmp (deptype->type, "ineed") == 0) - { - fprintf (stderr, - "Service `%s' needs non existant service `%s'\n", - depinfo->service, service); - } - continue; - } - - /* Add our deptype now */ - last_deptype = NULL; - for (dt = di->depends; dt; dt = dt->next) - { - last_deptype = dt; - if (strcmp (dt->type, deppairs[i].addto) == 0) - break; - } - if (! dt) - { - if (! last_deptype) - { - di->depends = xmalloc (sizeof (rc_deptype_t)); - dt = di->depends; - } - else - { - last_deptype->next = xmalloc (sizeof (rc_deptype_t)); - dt = last_deptype->next; - } - memset (dt, 0, sizeof (rc_deptype_t)); - dt->type = xstrdup (deppairs[i].addto); - } - - already_added = false; - STRLIST_FOREACH (dt->services, service, k) - if (strcmp (service, depinfo->service) == 0) - { - already_added = true; - break; - } - - if (! already_added) - rc_strlist_addsort (&dt->services, depinfo->service); - } - } - } - - /* Phase 5 - save to disk - Now that we're purely in C, do we need to keep a shell parseable file? - I think yes as then it stays human readable - This works and should be entirely shell parseable provided that depend - names don't have any non shell variable characters in - */ - if ((fp = fopen (RC_DEPTREE, "w"))) { - i = 0; - for (depinfo = deptree; depinfo; depinfo = depinfo->next) - { - fprintf (fp, "depinfo_%d_service='%s'\n", i, depinfo->service); - for (deptype = depinfo->depends; deptype; deptype = deptype->next) - { - k = 0; - STRLIST_FOREACH (deptype->services, service, j) - { - fprintf (fp, "depinfo_%d_%s_%d='%s'\n", i, deptype->type, - k, service); - k++; - } - } - i++; - } - fclose (fp); - } else { - fprintf (stderr, "fopen `%s': %s\n", RC_DEPTREE, strerror (errno)); - retval = false; - } - - /* Save our external config files to disk */ - if (config) { - if ((fp = fopen (RC_DEPCONFIG, "w"))) { - STRLIST_FOREACH (config, service, i) - fprintf (fp, "%s\n", service); - fclose (fp); - } else { - fprintf (stderr, "fopen `%s': %s\n", RC_DEPCONFIG, strerror (errno)); - retval = false; - } - rc_strlist_free (config); - } - - rc_deptree_free (deptree); - - return (retval); -} -librc_hidden_def(rc_deptree_update) diff --git a/src/librc-depend.h b/src/librc-depend.h deleted file mode 100644 index 238f70d1..00000000 --- a/src/librc-depend.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * librc-depend.h - * Internal header file for dependency structures - */ - -/* - * 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_DEPEND_H -#define _LIBRC_DEPEND_H - -/*! @name Dependency structures - * private to librc - rc.h exposes them just a pointers */ - -/*! Singly linked list of dependency types that list the services the - * type is for */ -typedef struct rc_deptype -{ - /*! ineed, iuse, iafter, etc */ - char *type; - /*! NULL terminated list of services */ - char **services; - /*! Next dependency type */ - struct rc_deptype *next; -} rc_deptype_t; - -/*! Singly linked list of services and their dependencies */ -typedef struct rc_depinfo -{ - /*! Name of service */ - char *service; - /*! Dependencies */ - rc_deptype_t *depends; - /*! Next service dependency type */ - struct rc_depinfo *next; -} rc_depinfo_t; - -#endif diff --git a/src/librc-misc.c b/src/librc-misc.c deleted file mode 100644 index dcecc293..00000000 --- a/src/librc-misc.c +++ /dev/null @@ -1,245 +0,0 @@ -/* - rc-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 "librc.h" - -bool rc_yesno (const char *value) -{ - if (! value) { - errno = ENOENT; - return (false); - } - - if (strcasecmp (value, "yes") == 0 || - strcasecmp (value, "y") == 0 || - strcasecmp (value, "true") == 0 || - strcasecmp (value, "1") == 0) - return (true); - - if (strcasecmp (value, "no") != 0 && - strcasecmp (value, "n") != 0 && - strcasecmp (value, "false") != 0 && - strcasecmp (value, "0") != 0) - errno = EINVAL; - - return (false); -} -librc_hidden_def(rc_yesno) - -char *rc_strcatpaths (const char *path1, const char *paths, ...) -{ - va_list ap; - int length; - int i; - char *p; - char *path; - char *pathp; - - if (! path1 || ! paths) - return (NULL); - - length = strlen (path1) + strlen (paths) + 1; - if (*paths != '/') - length ++; - - va_start (ap, paths); - while ((p = va_arg (ap, char *)) != NULL) { - if (*p != '/') - length ++; - length += strlen (p); - } - va_end (ap); - - pathp = path = xmalloc (length * sizeof (char)); - memset (path, 0, length); - i = strlen (path1); - memcpy (path, path1, i); - pathp += i; - if (*paths != '/') - *pathp ++ = '/'; - i = strlen (paths); - memcpy (pathp, paths, i); - pathp += i; - - va_start (ap, paths); - while ((p = va_arg (ap, char *)) != NULL) { - if (*p != '/') - *pathp ++= '/'; - i = strlen (p); - memcpy (pathp, p, i); - pathp += i; - } - va_end (ap); - - *pathp++ = 0; - - return (path); -} -librc_hidden_def(rc_strcatpaths) - - -char **rc_config_load (const char *file) -{ - char **list = NULL; - FILE *fp; - char *buffer; - char *p; - char *token; - char *line; - char *linep; - char *linetok; - int i = 0; - bool replaced; - char *entry; - char *newline; - - if (! (fp = fopen (file, "r"))) - return (NULL); - - buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); - while (fgets (buffer, RC_LINEBUFFER, fp)) { - p = buffer; - - /* Strip leading spaces/tabs */ - while ((*p == ' ') || (*p == '\t')) - p++; - - if (! p || strlen (p) < 3 || p[0] == '#') - continue; - - /* Get entry */ - token = strsep (&p, "="); - - if (! token) - continue; - - entry = xstrdup (token); - - /* Preserve shell coloring */ - if (*p == '$') - token = p; - else - do { - /* Bash variables are usually quoted */ - token = strsep (&p, "\"\'"); - } while ((token) && (strlen (token) == 0)); - - /* Drop a newline if that's all we have */ - i = strlen (token) - 1; - if (token[i] == 10) - token[i] = 0; - - i = strlen (entry) + strlen (token) + 2; - newline = xmalloc (i); - snprintf (newline, i, "%s=%s", entry, token); - - replaced = false; - /* In shells the last item takes precedence, so we need to remove - any prior values we may already have */ - STRLIST_FOREACH (list, line, i) { - char *tmp = xstrdup (line); - linep = tmp; - linetok = strsep (&linep, "="); - if (strcmp (linetok, entry) == 0) { - /* We have a match now - to save time we directly replace it */ - free (list[i - 1]); - list[i - 1] = newline; - replaced = true; - free (tmp); - break; - } - free (tmp); - } - - if (! replaced) { - rc_strlist_addsort (&list, newline); - free (newline); - } - free (entry); - } - free (buffer); - fclose (fp); - - return (list); -} -librc_hidden_def(rc_config_load) - -char *rc_config_value (char **list, const char *entry) -{ - char *line; - int i; - char *p; - - STRLIST_FOREACH (list, line, i) { - p = strchr (line, '='); - if (p && strncmp (entry, line, p - line) == 0) - return (p += 1); - } - - return (NULL); -} -librc_hidden_def(rc_config_value) - -char **rc_config_list (const char *file) -{ - FILE *fp; - char *buffer; - char *p; - char *token; - char **list = NULL; - - if (! (fp = fopen (file, "r"))) - return (NULL); - - buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); - while (fgets (buffer, RC_LINEBUFFER, fp)) { - p = buffer; - - /* Strip leading spaces/tabs */ - while ((*p == ' ') || (*p == '\t')) - p++; - - /* Get entry - we do not want comments */ - token = strsep (&p, "#"); - if (token && (strlen (token) > 1)) { - /* Stip the newline if present */ - if (token[strlen (token) - 1] == '\n') - token[strlen (token) - 1] = 0; - - rc_strlist_add (&list, token); - } - } - free (buffer); - fclose (fp); - - return (list); -} -librc_hidden_def(rc_config_list) diff --git a/src/librc-strlist.c b/src/librc-strlist.c deleted file mode 100644 index 815c8370..00000000 --- a/src/librc-strlist.c +++ /dev/null @@ -1,230 +0,0 @@ -/* - librc-strlist.h - String list functions for using char ** arrays - - Based on a previous implementation by Martin Schlemmer - */ - -/* - * 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 "librc.h" - -static char *_rc_strlist_add (char ***list, const char *item, bool uniq) -{ - char **newlist; - char **lst = *list; - int i = 0; - - if (! item) - return (NULL); - - while (lst && lst[i]) { - if (uniq && strcmp (lst[i], item) == 0) { - errno = EEXIST; - return (NULL); - } - i++; - } - - newlist = xrealloc (lst, sizeof (char *) * (i + 2)); - newlist[i] = xstrdup (item); - newlist[i + 1] = NULL; - - *list = newlist; - return (newlist[i]); -} - -char *rc_strlist_add (char ***list, const char *item) -{ - return (_rc_strlist_add (list, item, false)); -} -librc_hidden_def(rc_strlist_add) - -char *rc_strlist_addu (char ***list, const char *item) -{ - return (_rc_strlist_add (list, item, true)); -} -librc_hidden_def(rc_strlist_addu) - -static char *_rc_strlist_addsort (char ***list, const char *item, - int (*sortfunc) (const char *s1, - const char *s2), - bool uniq) -{ - char **newlist; - char **lst = *list; - int i = 0; - char *tmp1; - char *tmp2; - char *retval; - - if (! item) - return (NULL); - - while (lst && lst[i]) { - if (uniq && strcmp (lst[i], item) == 0) { - errno = EEXIST; - return (NULL); - } - i++; - } - - newlist = xrealloc (lst, sizeof (char *) * (i + 2)); - - if (! i) - newlist[i] = NULL; - newlist[i + 1] = NULL; - - i = 0; - while (newlist[i] && sortfunc (newlist[i], item) < 0) - i++; - - tmp1 = newlist[i]; - retval = newlist[i] = xstrdup (item); - do { - i++; - tmp2 = newlist[i]; - newlist[i] = tmp1; - tmp1 = tmp2; - } while (tmp1); - - *list = newlist; - return (retval); -} - -char *rc_strlist_addsort (char ***list, const char *item) -{ - return (_rc_strlist_addsort (list, item, strcoll, false)); -} -librc_hidden_def(rc_strlist_addsort) - -char *rc_strlist_addsortc (char ***list, const char *item) -{ - return (_rc_strlist_addsort (list, item, strcmp, false)); -} -librc_hidden_def(rc_strlist_addsortc) - -char *rc_strlist_addsortu (char ***list, const char *item) -{ - return (_rc_strlist_addsort (list, item, strcmp, true)); -} -librc_hidden_def(rc_strlist_addsortu) - -bool rc_strlist_delete (char ***list, const char *item) -{ - char **lst = *list; - int i = 0; - - if (!lst || ! item) - return (false); - - while (lst[i]) { - if (strcmp (lst[i], item) == 0) { - free (lst[i]); - do { - lst[i] = lst[i + 1]; - i++; - } while (lst[i]); - return (true); - } - i++; - } - - errno = ENOENT; - return (false); -} -librc_hidden_def(rc_strlist_delete) - -char *rc_strlist_join (char ***list1, char **list2) -{ - char **lst1 = *list1; - char **newlist; - int i = 0; - int j = 0; - - if (! list2) - return (NULL); - - while (lst1 && lst1[i]) - i++; - - while (list2[j]) - j++; - - newlist = xrealloc (lst1, sizeof (char *) * (i + j + 1)); - - j = 0; - while (list2[j]) { - newlist[i] = list2[j]; - /* Take the item off the 2nd list as it's only a shallow copy */ - list2[j] = NULL; - i++; - j++; - } - newlist[i] = NULL; - - *list1 = newlist; - return (newlist[i == 0 ? 0 : i - 1]); -} -librc_hidden_def(rc_strlist_join) - -void rc_strlist_reverse (char **list) -{ - char *item; - int i = 0; - int j = 0; - - if (! list) - return; - - while (list[j]) - j++; - j--; - - while (i < j && list[i] && list[j]) { - item = list[i]; - list[i] = list[j]; - list[j] = item; - i++; - j--; - } -} -librc_hidden_def(rc_strlist_reverse) - -void rc_strlist_free (char **list) -{ - int i = 0; - - if (! list) - return; - - while (list[i]) - free (list[i++]); - - free (list); -} -librc_hidden_def(rc_strlist_free) diff --git a/src/librc.c b/src/librc.c deleted file mode 100644 index 15309f87..00000000 --- a/src/librc.c +++ /dev/null @@ -1,891 +0,0 @@ -/* - librc - core RC functions - */ - -/* - * Copyright 2007-2008 Roy Marples - * All rights reserved - - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -const char librc_copyright[] = "Copyright (c) 2007-2008 Roy Marples"; - -#include "librc.h" - -#define SOFTLEVEL RC_SVCDIR "/softlevel" - -#ifndef S_IXUGO -# define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH) -#endif - -/* File stream used for plugins to write environ vars to */ -FILE *rc_environ_fd = NULL; - -typedef struct rc_service_state_name { - rc_service_state_t state; - const char *name; -} rc_service_state_name_t; - -/* We MUST list the states below 0x10 first - * The rest can be in any order */ -static const rc_service_state_name_t rc_service_state_names[] = { - { RC_SERVICE_STARTED, "started" }, - { RC_SERVICE_STOPPED, "stopped" }, - { RC_SERVICE_STARTING, "starting" }, - { RC_SERVICE_STOPPING, "stopping" }, - { RC_SERVICE_INACTIVE, "inactive" }, - { RC_SERVICE_WASINACTIVE, "wasinactive" }, - { RC_SERVICE_COLDPLUGGED, "coldplugged" }, - { RC_SERVICE_FAILED, "failed" }, - { RC_SERVICE_SCHEDULED, "scheduled"}, - { 0, NULL} -}; - -#define LS_INITD 0x01 -#define LS_DIR 0x02 -static char **ls_dir (const char *dir, int options) -{ - DIR *dp; - struct dirent *d; - char **list = NULL; - struct stat buf; - - if ((dp = opendir (dir)) == NULL) - return (NULL); - - while (((d = readdir (dp)) != NULL)) { - if (d->d_name[0] != '.') { - if (options & LS_INITD) { - int l = strlen (d->d_name); - - /* Check that our file really exists. - * This is important as a service maybe in a runlevel, but - * could also have been removed. */ - char *file = rc_strcatpaths (dir, d->d_name, NULL); - int ok = stat (file, &buf); - free (file); - if (ok != 0) - continue; - - /* .sh files are not init scripts */ - if (l > 2 && d->d_name[l - 3] == '.' && - d->d_name[l - 2] == 's' && - d->d_name[l - 1] == 'h') - continue; - } - if (options & LS_DIR) { - if (stat (d->d_name, &buf) == 0 && ! S_ISDIR (buf.st_mode)) - continue; - } - rc_strlist_addsort (&list, d->d_name); - } - } - closedir (dp); - - return (list); -} - -static bool rm_dir (const char *pathname, bool top) -{ - DIR *dp; - struct dirent *d; - - if ((dp = opendir (pathname)) == NULL) - return (false); - - errno = 0; - while (((d = readdir (dp)) != NULL) && errno == 0) { - if (strcmp (d->d_name, ".") != 0 && strcmp (d->d_name, "..") != 0) { - char *tmp = rc_strcatpaths (pathname, d->d_name, (char *) NULL); - if (d->d_type == DT_DIR) { - if (! rm_dir (tmp, true)) - { - free (tmp); - closedir (dp); - return (false); - } - } else { - if (unlink (tmp)) { - free (tmp); - closedir (dp); - return (false); - } - } - free (tmp); - } - } - closedir (dp); - - if (top && rmdir (pathname) != 0) - return (false); - - return (true); -} - -static const char *rc_parse_service_state (rc_service_state_t state) -{ - int i; - - for (i = 0; rc_service_state_names[i].name; i++) { - if (rc_service_state_names[i].state == state) - return (rc_service_state_names[i].name); - } - - return (NULL); -} - -bool rc_runlevel_starting (void) -{ - return (exists (RC_STARTING)); -} -librc_hidden_def(rc_runlevel_starting) - -bool rc_runlevel_stopping (void) -{ - return (exists (RC_STOPPING)); -} -librc_hidden_def(rc_runlevel_stopping) - -char **rc_runlevel_list (void) -{ - return (ls_dir (RC_RUNLEVELDIR, LS_DIR)); -} -librc_hidden_def(rc_runlevel_list) - -char *rc_runlevel_get (void) -{ - FILE *fp; - char *runlevel = NULL; - - if ((fp = fopen (SOFTLEVEL, "r"))) { - runlevel = xmalloc (sizeof (char) * PATH_MAX); - if (fgets (runlevel, PATH_MAX, fp)) { - int i = strlen (runlevel) - 1; - if (runlevel[i] == '\n') - runlevel[i] = 0; - } else - *runlevel = '\0'; - fclose (fp); - } - - if (! runlevel || ! *runlevel) { - free (runlevel); - runlevel = xstrdup (RC_LEVEL_SYSINIT); - } - - return (runlevel); -} -librc_hidden_def(rc_runlevel_get) - -bool rc_runlevel_set (const char *runlevel) -{ - FILE *fp = fopen (SOFTLEVEL, "w"); - - if (! fp) - return (false); - fprintf (fp, "%s", runlevel); - fclose (fp); - return (true); -} -librc_hidden_def(rc_runlevel_set) - -bool rc_runlevel_exists (const char *runlevel) -{ - char *path; - struct stat buf; - bool retval = false; - - if (! runlevel) - return (false); - - path = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, (char *) NULL); - if (stat (path, &buf) == 0 && S_ISDIR (buf.st_mode)) - retval = true; - free (path); - return (retval); -} -librc_hidden_def(rc_runlevel_exists) - -/* Resolve a service name to it's full path */ -char *rc_service_resolve (const char *service) -{ - char buffer[PATH_MAX]; - char *file; - int r = 0; - struct stat buf; - - if (! service) - return (NULL); - - if (service[0] == '/') - return (xstrdup (service)); - - file = rc_strcatpaths (RC_SVCDIR, "started", service, (char *) NULL); - if (lstat (file, &buf) || ! S_ISLNK (buf.st_mode)) { - free (file); - file = rc_strcatpaths (RC_SVCDIR, "inactive", service, (char *) NULL); - if (lstat (file, &buf) || ! S_ISLNK (buf.st_mode)) { - free (file); - file = NULL; - } - } - - memset (buffer, 0, sizeof (buffer)); - if (file) { - r = readlink (file, buffer, sizeof (buffer)); - free (file); - if (r > 0) - return (xstrdup (buffer)); - } - snprintf (buffer, sizeof (buffer), RC_INITDIR "/%s", service); - - /* So we don't exist in /etc/init.d - check /usr/local/etc/init.d */ - if (stat (buffer, &buf) != 0) { - snprintf (buffer, sizeof (buffer), RC_INITDIR_LOCAL "/%s", service); - if (stat (buffer, &buf) != 0) - return (NULL); - } - - return (xstrdup (buffer)); -} -librc_hidden_def(rc_service_resolve) - -bool rc_service_exists (const char *service) -{ - char *file; - bool retval = false; - int len; - struct stat buf; - - if (! service) - return (false); - - len = strlen (service); - - /* .sh files are not init scripts */ - if (len > 2 && service[len - 3] == '.' && - service[len - 2] == 's' && - service[len - 1] == 'h') - return (false); - - file = rc_service_resolve (service); - if (stat (file, &buf) == 0 && buf.st_mode & S_IXUGO) - retval = true; - free (file); - return (retval); -} -librc_hidden_def(rc_service_exists) - -#define OPTSTR ". '%s'; echo \"${opts}\"" -char **rc_service_extra_commands (const char *service) -{ - char *svc; - char *cmd = NULL; - char *buffer = NULL; - char **commands = NULL; - char *token; - char *p = buffer; - FILE *fp; - int l; - - if (! (svc = rc_service_resolve (service))) - return (NULL); - - l = strlen (OPTSTR) + strlen (svc) + 1; - cmd = xmalloc (sizeof (char) * l); - snprintf (cmd, l, OPTSTR, svc); - free (svc); - if ((fp = popen (cmd, "r"))) { - buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); - if (fgets (buffer, RC_LINEBUFFER, fp)) { - if (buffer[strlen (buffer) - 1] == '\n') - buffer[strlen (buffer) - 1] = '\0'; - while ((token = strsep (&p, " "))) - rc_strlist_addsort (&commands, token); - } - pclose (fp); - free (buffer); - } - free (cmd); - return (commands); -} -librc_hidden_def(rc_service_extra_commands) - -#define DESCSTR ". '%s'; echo \"${description%s%s}\"" -char *rc_service_description (const char *service, const char *option) -{ - char *svc; - char *cmd = NULL; - char *buffer; - char *desc = NULL; - FILE *fp; - int i; - int l; - - if (! (svc = rc_service_resolve (service))) - return (NULL); - - if (! option) - option = ""; - - l = strlen (DESCSTR) + strlen (svc) + strlen (option) + 2; - cmd = xmalloc (sizeof (char) * l); - snprintf (cmd, l, DESCSTR, svc, option ? "_" : "", option); - free (svc); - if ((fp = popen (cmd, "r"))) { - buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); - while (fgets (buffer, RC_LINEBUFFER, fp)) { - if (! desc) { - desc = xmalloc (strlen (buffer) + 1); - *desc = '\0'; - } else { - desc = xrealloc (desc, strlen (desc) + strlen (buffer) + 1); - } - i = strlen (desc); - memcpy (desc + i, buffer, strlen (buffer)); - memset (desc + i + strlen (buffer), 0, 1); - } - free (buffer); - pclose (fp); - } - free (cmd); - return (desc); -} -librc_hidden_def(rc_service_description) - -bool rc_service_in_runlevel (const char *service, const char *runlevel) -{ - char *file; - bool retval; - - if (! runlevel || ! service) - return (false); - - file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename_c (service), - (char *) NULL); - retval = exists (file); - free (file); - - return (retval); -} -librc_hidden_def(rc_service_in_runlevel) - -bool rc_service_mark (const char *service, const rc_service_state_t state) -{ - char *file; - int i = 0; - int skip_state = -1; - const char *base; - char *init = rc_service_resolve (service); - bool skip_wasinactive = false; - - if (! init) - return (false); - - base = basename_c (service); - - if (state != RC_SERVICE_STOPPED) { - if (! exists (init)) { - free (init); - return (false); - } - - file = rc_strcatpaths (RC_SVCDIR, rc_parse_service_state (state), base, - (char *) NULL); - if (exists (file)) - unlink (file); - i = symlink (init, file); - if (i != 0) { - free (file); - free (init); - return (false); - } - - free (file); - skip_state = state; - } - - if (state == RC_SERVICE_COLDPLUGGED || state == RC_SERVICE_FAILED) { - free (init); - return (true); - } - - /* Remove any old states now */ - for (i = 0; rc_service_state_names[i].name; i++) { - int s = rc_service_state_names[i].state; - - if ((s != skip_state && - s != RC_SERVICE_STOPPED && - s != RC_SERVICE_COLDPLUGGED && - s != RC_SERVICE_SCHEDULED) && - (! skip_wasinactive || s != RC_SERVICE_WASINACTIVE)) - { - file = rc_strcatpaths (RC_SVCDIR, rc_parse_service_state (s), base, - (char *) NULL); - if (exists (file)) { - if ((state == RC_SERVICE_STARTING || - state == RC_SERVICE_STOPPING) && - s == RC_SERVICE_INACTIVE) - { - char *wasfile = rc_strcatpaths (RC_SVCDIR, - rc_parse_service_state (RC_SERVICE_WASINACTIVE), - base, (char *) NULL); - - symlink (init, wasfile); - skip_wasinactive = true; - free (wasfile); - } - unlink (file); - } - free (file); - } - } - - /* Remove the exclusive state if we're inactive */ - if (state == RC_SERVICE_STARTED || - state == RC_SERVICE_STOPPED || - state == RC_SERVICE_INACTIVE) - { - file = rc_strcatpaths (RC_SVCDIR, "exclusive", base, (char *) NULL); - unlink (file); - free (file); - } - - /* Remove any options and daemons the service may have stored */ - if (state == RC_SERVICE_STOPPED) { - char *dir = rc_strcatpaths (RC_SVCDIR, "options", base, (char *) NULL); - rm_dir (dir, true); - free (dir); - - dir = rc_strcatpaths (RC_SVCDIR, "daemons", base, (char *) NULL); - rm_dir (dir, true); - free (dir); - - rc_service_schedule_clear (service); - } - - /* These are final states, so remove us from scheduled */ - if (state == RC_SERVICE_STARTED || state == RC_SERVICE_STOPPED) { - char *sdir = rc_strcatpaths (RC_SVCDIR, "scheduled", (char *) NULL); - char **dirs = ls_dir (sdir, 0); - char *dir; - int serrno; - - STRLIST_FOREACH (dirs, dir, i) { - char *bdir = rc_strcatpaths (sdir, dir, (char *) NULL); - file = rc_strcatpaths (bdir, base, (char *) NULL); - unlink (file); - free (file); - - /* Try and remove the dir - we don't care about errors */ - serrno = errno; - rmdir (bdir); - errno = serrno; - free (bdir); - } - rc_strlist_free (dirs); - free (sdir); - } - - free (init); - return (true); -} -librc_hidden_def(rc_service_mark) - -rc_service_state_t rc_service_state (const char *service) -{ - int i; - int state = RC_SERVICE_STOPPED; - - for (i = 0; rc_service_state_names[i].name; i++) { - char *file = rc_strcatpaths (RC_SVCDIR, rc_service_state_names[i].name, - basename_c (service), (char*) NULL); - if (exists (file)) { - if (rc_service_state_names[i].state <= 0x10) - state = rc_service_state_names[i].state; - else - state |= rc_service_state_names[i].state; - } - free (file); - } - - if (state & RC_SERVICE_STOPPED) { - char **services = rc_services_scheduled_by (service); - if (services) { - state |= RC_SERVICE_SCHEDULED; - free (services); - } - } - - return (state); -} -librc_hidden_def(rc_service_state) - -char *rc_service_value_get (const char *service, const char *option) -{ - FILE *fp; - char *buffer = NULL; - char *file = rc_strcatpaths (RC_SVCDIR, "options", service, option, - (char *) NULL); - - if ((fp = fopen (file, "r"))) { - buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); - fgets (buffer, RC_LINEBUFFER, fp); - fclose (fp); - } - free (file); - - return (buffer); -} -librc_hidden_def(rc_service_value_get) - -bool rc_service_value_set (const char *service, const char *option, - const char *value) -{ - FILE *fp; - char *path = rc_strcatpaths (RC_SVCDIR, "options", service, (char *) NULL); - char *file = rc_strcatpaths (path, option, (char *) NULL); - bool retval = false; - - if (mkdir (path, 0755) != 0 && errno != EEXIST) { - free (path); - free (file); - return (false); - } - - if ((fp = fopen (file, "w"))) { - if (value) - fprintf (fp, "%s", value); - fclose (fp); - retval = true; - } - - free (path); - free (file); - return (retval); -} -librc_hidden_def(rc_service_value_set) - -static pid_t _exec_service (const char *service, const char *arg) -{ - char *file; - char *fifo; - pid_t pid = -1; - - file = rc_service_resolve (service); - if (! exists (file)) { - rc_service_mark (service, RC_SERVICE_STOPPED); - free (file); - return (0); - } - - /* We create a fifo so that other services can wait until we complete */ - fifo = rc_strcatpaths (RC_SVCDIR, "exclusive", basename_c (service), - (char *) NULL); - - if (mkfifo (fifo, 0600) != 0 && errno != EEXIST) { - free (fifo); - free (file); - return (-1); - } - - if ((pid = vfork ()) == 0) { - execl (file, file, arg, (char *) NULL); - fprintf (stderr, "unable to exec `%s': %s\n", file, strerror (errno)); - unlink (fifo); - _exit (EXIT_FAILURE); - } - - free (fifo); - free (file); - - if (pid == -1) - fprintf (stderr, "vfork: %s\n", strerror (errno)); - - return (pid); -} - -pid_t rc_service_stop (const char *service) -{ - rc_service_state_t state = rc_service_state (service); - - if (state & RC_SERVICE_FAILED) - return (-1); - - if (state & RC_SERVICE_STOPPED) - return (0); - - return (_exec_service (service, "stop")); -} -librc_hidden_def(rc_service_stop) - -pid_t rc_service_start (const char *service) -{ - rc_service_state_t state = rc_service_state (service); - - if (state & RC_SERVICE_FAILED) - return (-1); - - if (! state & RC_SERVICE_STOPPED) - return (0); - - return (_exec_service (service, "start")); -} -librc_hidden_def(rc_service_start) - -bool rc_service_schedule_start (const char *service, - const char *service_to_start) -{ - char *dir; - char *init; - char *file; - bool retval; - - /* service may be a provided service, like net */ - if (! service || ! rc_service_exists (service_to_start)) - return (false); - - dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename_c (service), - (char *) NULL); - if (mkdir (dir, 0755) != 0 && errno != EEXIST) { - free (dir); - return (false); - } - - init = rc_service_resolve (service_to_start); - file = rc_strcatpaths (dir, basename_c (service_to_start), (char *) NULL); - retval = (exists (file) || symlink (init, file) == 0); - free (init); - free (file); - free (dir); - - return (retval); -} -librc_hidden_def(rc_service_schedule_start) - -bool rc_service_schedule_clear (const char *service) -{ - char *dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename_c (service), - (char *) NULL); - bool retval; - - if (! (retval = rm_dir (dir, true)) && errno == ENOENT) - retval = true; - free (dir); - return (retval); -} -librc_hidden_def(rc_service_schedule_clear) - - -char **rc_services_in_runlevel (const char *runlevel) -{ - char *dir; - char **list = NULL; - - if (! runlevel) { - int i; - char **local = ls_dir (RC_INITDIR_LOCAL, LS_INITD); - - list = ls_dir (RC_INITDIR, LS_INITD); - STRLIST_FOREACH (local, dir, i) - rc_strlist_addsortu (&list, dir); - rc_strlist_free (local); - return (list); - } - - /* These special levels never contain any services */ - if (strcmp (runlevel, RC_LEVEL_SYSINIT) == 0 || - strcmp (runlevel, RC_LEVEL_SINGLE) == 0) - return (NULL); - - dir = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, (char *) NULL); - list = ls_dir (dir, LS_INITD); - free (dir); - return (list); -} -librc_hidden_def(rc_services_in_runlevel) - -char **rc_services_in_state (rc_service_state_t state) -{ - char *dir = rc_strcatpaths (RC_SVCDIR, rc_parse_service_state (state), - (char *) NULL); - char **list = NULL; - - if (state == RC_SERVICE_SCHEDULED) { - char **dirs = ls_dir (dir, 0); - char *d; - int i; - - STRLIST_FOREACH (dirs, d, i) { - char *p = rc_strcatpaths (dir, d, (char *) NULL); - char **entries = ls_dir (p, LS_INITD); - char *e; - int j; - - STRLIST_FOREACH (entries, e, j) - rc_strlist_addsortu (&list, e); - - if (entries) - free (entries); - } - - if (dirs) - free (dirs); - } else { - list = ls_dir (dir, LS_INITD); - } - - free (dir); - return (list); -} -librc_hidden_def(rc_services_in_state) - -bool rc_service_add (const char *runlevel, const char *service) -{ - bool retval; - char *init; - char *file; - - if (! rc_runlevel_exists (runlevel)) { - errno = ENOENT; - return (false); - } - - if (rc_service_in_runlevel (service, runlevel)) { - errno = EEXIST; - return (false); - } - - init = rc_service_resolve (service); - - /* We need to ensure that only things in /etc/init.d are added - * to the boot runlevel */ - if (strcmp (runlevel, RC_LEVEL_BOOT) == 0) { - char *tmp = xstrdup (init); - retval = (strcmp (dirname (tmp), RC_INITDIR) == 0); - free (tmp); - if (! retval) { - free (init); - errno = EPERM; - return (false); - } - } - - file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename_c (service), - (char *) NULL); - retval = (symlink (init, file) == 0); - free (init); - free (file); - return (retval); -} -librc_hidden_def(rc_service_add) - -bool rc_service_delete (const char *runlevel, const char *service) -{ - char *file; - bool retval = false; - - if (! runlevel || ! service) - return (false); - - file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename_c (service), - (char *) NULL); - if (unlink (file) == 0) - retval = true; - - free (file); - return (retval); -} -librc_hidden_def(rc_service_delete) - -char **rc_services_scheduled_by (const char *service) -{ - char **dirs = ls_dir (RC_SVCDIR "/scheduled", 0); - char **list = NULL; - char *dir; - int i; - - STRLIST_FOREACH (dirs, dir, i) { - char *file = rc_strcatpaths (RC_SVCDIR, "scheduled", dir, service, - (char *) NULL); - if (exists (file)) - rc_strlist_add (&list, file); - free (file); - } - rc_strlist_free (dirs); - - return (list); -} -librc_hidden_def(rc_services_scheduled_by) - -char **rc_services_scheduled (const char *service) -{ - char *dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename_c (service), - (char *) NULL); - char **list = NULL; - - list = ls_dir (dir, LS_INITD); - free (dir); - return (list); -} -librc_hidden_def(rc_services_scheduled) - -bool rc_service_plugable (const char *service) -{ - char *list; - char *p; - char *star; - char *token; - bool allow = true; - char *match = getenv ("RC_PLUG_SERVICES"); - if (! match) - return true; - - list = xstrdup (match); - p = list; - while ((token = strsep (&p, " "))) { - bool truefalse = true; - if (token[0] == '!') { - truefalse = false; - token++; - } - - star = strchr (token, '*'); - if (star) { - if (strncmp (service, token, star - token) == 0) { - allow = truefalse; - break; - } - } else { - if (strcmp (service, token) == 0) { - allow = truefalse; - break; - } - } - } - - free (list); - return (allow); -} -librc_hidden_def(rc_service_plugable) diff --git a/src/librc.h b/src/librc.h deleted file mode 100644 index cf61217a..00000000 --- a/src/librc.h +++ /dev/null @@ -1,127 +0,0 @@ -/* - * librc.h - * Internal header file to setup build env for files in librc.so - */ - -/* - * 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_H_ -#define _LIBRC_H_ - -#define _IN_LIBRC - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(__DragonFly__) || defined(__FreeBSD__) || \ - defined(__NetBSD__) || defined (__OpenBSD__) -#include -#include -#include -#include -#endif - -#include "librc-depend.h" -#include "rc.h" -#include "rc-misc.h" -#include "strlist.h" - -#include "hidden-visibility.h" -#define librc_hidden_proto(x) hidden_proto(x) -#define librc_hidden_def(x) hidden_def(x) - -librc_hidden_proto(rc_config_list) -librc_hidden_proto(rc_config_load) -librc_hidden_proto(rc_config_value) -librc_hidden_proto(rc_deptree_depend) -librc_hidden_proto(rc_deptree_depends) -librc_hidden_proto(rc_deptree_free) -librc_hidden_proto(rc_deptree_load) -librc_hidden_proto(rc_deptree_order) -librc_hidden_proto(rc_deptree_update) -librc_hidden_proto(rc_deptree_update_needed) -librc_hidden_proto(rc_find_pids) -librc_hidden_proto(rc_runlevel_exists) -librc_hidden_proto(rc_runlevel_get) -librc_hidden_proto(rc_runlevel_list) -librc_hidden_proto(rc_runlevel_set) -librc_hidden_proto(rc_runlevel_starting) -librc_hidden_proto(rc_runlevel_stopping) -librc_hidden_proto(rc_service_add) -librc_hidden_proto(rc_service_daemons_crashed) -librc_hidden_proto(rc_service_daemon_set) -librc_hidden_proto(rc_service_delete) -librc_hidden_proto(rc_service_description) -librc_hidden_proto(rc_service_exists) -librc_hidden_proto(rc_service_extra_commands) -librc_hidden_proto(rc_service_in_runlevel) -librc_hidden_proto(rc_service_mark) -librc_hidden_proto(rc_service_plugable) -librc_hidden_proto(rc_service_resolve) -librc_hidden_proto(rc_service_schedule_clear) -librc_hidden_proto(rc_service_schedule_start) -librc_hidden_proto(rc_service_start) -librc_hidden_proto(rc_service_stop) -librc_hidden_proto(rc_services_in_runlevel) -librc_hidden_proto(rc_services_in_state) -librc_hidden_proto(rc_services_scheduled) -librc_hidden_proto(rc_services_scheduled_by) -librc_hidden_proto(rc_service_started_daemon) -librc_hidden_proto(rc_service_state) -librc_hidden_proto(rc_service_value_get) -librc_hidden_proto(rc_service_value_set) -librc_hidden_proto(rc_strcatpaths) -librc_hidden_proto(rc_strlist_add) -librc_hidden_proto(rc_strlist_addu) -librc_hidden_proto(rc_strlist_addsort) -librc_hidden_proto(rc_strlist_addsortc) -librc_hidden_proto(rc_strlist_addsortu) -librc_hidden_proto(rc_strlist_delete) -librc_hidden_proto(rc_strlist_free) -librc_hidden_proto(rc_strlist_join) -librc_hidden_proto(rc_strlist_reverse) -librc_hidden_proto(rc_yesno) - -#endif diff --git a/src/librc/Makefile b/src/librc/Makefile new file mode 100644 index 00000000..27ea942c --- /dev/null +++ b/src/librc/Makefile @@ -0,0 +1,14 @@ +TOPDIR= .. +include $(TOPDIR)/os.mk + +LIB= rc +SHLIB_MAJOR= 1 +SRCS= librc.c librc-daemon.c librc-depend.c librc-misc.c librc-strlist.c +INCS= rc.h + +CPPFLAGS+= -DLIB=\"${LIBNAME}\" + +SHLIBDIR= /${LIBNAME} + +include $(TOPDIR)/cc.mk +include $(TOPDIR)/lib.mk diff --git a/src/librc/librc-daemon.c b/src/librc/librc-daemon.c new file mode 100644 index 00000000..fe9509de --- /dev/null +++ b/src/librc/librc-daemon.c @@ -0,0 +1,574 @@ +/* + librc-daemon + Finds PID for given daemon criteria + */ + +/* + * 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 "librc.h" + +#if defined(__linux__) +static bool pid_is_cmd (pid_t pid, const char *cmd) +{ + char buffer[32]; + FILE *fp; + int c; + + snprintf(buffer, sizeof (buffer), "/proc/%d/stat", pid); + if ((fp = fopen (buffer, "r")) == NULL) + return (false); + + while ((c = getc (fp)) != EOF && c != '(') + ; + + if (c != '(') { + fclose(fp); + return (false); + } + + while ((c = getc (fp)) != EOF && c == *cmd) + cmd++; + + fclose (fp); + + return ((c == ')' && *cmd == '\0') ? true : false); +} + +static bool pid_is_exec (pid_t pid, const char *exec) +{ + char cmdline[32]; + char buffer[PATH_MAX]; + char *p; + int fd = -1; + int r; + + snprintf (cmdline, sizeof (cmdline), "/proc/%u/exe", pid); + memset (buffer, 0, sizeof (buffer)); + if (readlink (cmdline, buffer, sizeof (buffer)) != -1) { + if (strcmp (exec, buffer) == 0) + return (true); + + /* We should cater for deleted binaries too */ + if (strlen (buffer) > 10) { + p = buffer + (strlen (buffer) - 10); + if (strcmp (p, " (deleted)") == 0) { + *p = 0; + if (strcmp (buffer, exec) == 0) + return (true); + } + } + } + + snprintf (cmdline, sizeof (cmdline), "/proc/%u/cmdline", pid); + if ((fd = open (cmdline, O_RDONLY)) < 0) + return (false); + + r = read(fd, buffer, sizeof (buffer)); + close (fd); + + if (r == -1) + return 0; + + buffer[r] = 0; + return (strcmp (exec, buffer) == 0 ? true : false); +} + +pid_t *rc_find_pids (const char *exec, const char *cmd, + uid_t uid, pid_t pid) +{ + DIR *procdir; + struct dirent *entry; + int npids = 0; + pid_t p; + pid_t *pids = NULL; + pid_t *tmp = NULL; + char buffer[PATH_MAX]; + struct stat sb; + pid_t runscript_pid = 0; + char *pp; + + if ((procdir = opendir ("/proc")) == NULL) + return (NULL); + + /* + We never match RC_RUNSCRIPT_PID if present so we avoid the below + scenario + + /etc/init.d/ntpd stop does + start-stop-daemon --stop --name ntpd + catching /etc/init.d/ntpd stop + + nasty + */ + + if ((pp = getenv ("RC_RUNSCRIPT_PID"))) { + if (sscanf (pp, "%d", &runscript_pid) != 1) + runscript_pid = 0; + } + + while ((entry = readdir (procdir)) != NULL) { + if (sscanf (entry->d_name, "%d", &p) != 1) + continue; + + if (runscript_pid != 0 && runscript_pid == p) + continue; + + if (pid != 0 && pid != p) + continue; + + if (uid) { + snprintf (buffer, sizeof (buffer), "/proc/%d", p); + if (stat (buffer, &sb) != 0 || sb.st_uid != uid) + continue; + } + + if (cmd && ! pid_is_cmd (p, cmd)) + continue; + + if (exec && ! cmd && ! pid_is_exec (p, exec)) + continue; + + tmp = realloc (pids, sizeof (pid_t) * (npids + 2)); + if (! tmp) { + free (pids); + closedir (procdir); + errno = ENOMEM; + return (NULL); + } + pids = tmp; + + pids[npids] = p; + pids[npids + 1] = 0; + npids++; + } + closedir (procdir); + + return (pids); +} +librc_hidden_def(rc_find_pids) + +#elif BSD + +# if defined(__DragonFly__) || defined(__FreeBSD__) +# ifndef KERN_PROC_PROC +# define KERN_PROC_PROC KERN_PROC_ALL +# endif +# define _KINFO_PROC kinfo_proc +# define _KVM_GETARGV kvm_getargv +# define _GET_KINFO_UID(kp) (kp.ki_ruid) +# define _GET_KINFO_COMM(kp) (kp.ki_comm) +# define _GET_KINFO_PID(kp) (kp.ki_pid) +# else +# define _KVM_GETPROC2 +# define _KINFO_PROC kinfo_proc2 +# define _KVM_GETARGV kvm_getargv2 +# define _GET_KINFO_UID(kp) (kp.p_ruid) +# define _GET_KINFO_COMM(kp) (kp.p_comm) +# define _GET_KINFO_PID(kp) (kp.p_pid) +# endif + +pid_t *rc_find_pids (const char *exec, const char *cmd, + uid_t uid, pid_t pid) +{ + static kvm_t *kd = NULL; + char errbuf[_POSIX2_LINE_MAX]; + struct _KINFO_PROC *kp; + int i; + int processes = 0; + int argc = 0; + char **argv; + pid_t *pids = NULL; + pid_t *tmp; + int npids = 0; + + if ((kd = kvm_openfiles (NULL, NULL, NULL, O_RDONLY, errbuf)) == NULL) { + fprintf (stderr, "kvm_open: %s", errbuf); + return (NULL); + } + +#ifdef _KVM_GETPROC2 + kp = kvm_getproc2 (kd, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc2), + &processes); +#else + kp = kvm_getprocs (kd, KERN_PROC_PROC, 0, &processes); +#endif + for (i = 0; i < processes; i++) { + pid_t p = _GET_KINFO_PID (kp[i]); + if (pid != 0 && pid != p) + continue; + + if (uid != 0 && uid != _GET_KINFO_UID (kp[i])) + continue; + + if (cmd) { + if (! _GET_KINFO_COMM (kp[i]) || + strcmp (cmd, _GET_KINFO_COMM (kp[i])) != 0) + continue; + } + + if (exec && ! cmd) { + if ((argv = _KVM_GETARGV (kd, &kp[i], argc)) == NULL || ! *argv) + continue; + + if (strcmp (*argv, exec) != 0) + continue; + } + + tmp = realloc (pids, sizeof (pid_t) * (npids + 2)); + if (! tmp) { + free (pids); + kvm_close (kd); + errno = ENOMEM; + return (NULL); + } + pids = tmp; + + pids[npids] = p; + pids[npids + 1] = 0; + npids++; + } + kvm_close (kd); + + return (pids); +} +librc_hidden_def(rc_find_pids) + +#else +# error "Platform not supported!" +#endif + +static bool _match_daemon (const char *path, const char *file, + const char *mexec, const char *mname, + const char *mpidfile) +{ + char *buffer; + char *ffile = rc_strcatpaths (path, file, (char *) NULL); + FILE *fp; + int lc = 0; + int m = 0; + + if ((fp = fopen (ffile, "r")) == NULL) { + free (ffile); + return (false); + } + + if (! mname) + m += 10; + if (! mpidfile) + m += 100; + + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + memset (buffer, 0, RC_LINEBUFFER); + while ((fgets (buffer, RC_LINEBUFFER, fp))) { + int lb = strlen (buffer) - 1; + if (buffer[lb] == '\n') + buffer[lb] = 0; + + if (strcmp (buffer, mexec) == 0) + m += 1; + else if (mname && strcmp (buffer, mname) == 0) + m += 10; + else if (mpidfile && strcmp (buffer, mpidfile) == 0) + m += 100; + + if (m == 111) + break; + + lc++; + if (lc > 5) + break; + } + free (buffer); + fclose (fp); + free (ffile); + + return (m == 111 ? true : false); +} + +bool rc_service_daemon_set (const char *service, const char *exec, + const char *name, const char *pidfile, + bool started) +{ + char *dirpath; + char *file = NULL; + int i; + char *mexec; + char *mname; + char *mpidfile; + int nfiles = 0; + char *oldfile = NULL; + bool retval = false; + DIR *dp; + struct dirent *d; + + if (! exec && ! name && ! pidfile) { + errno = EINVAL; + return (false); + } + + dirpath = rc_strcatpaths (RC_SVCDIR, "daemons", + basename_c (service), (char *) NULL); + + if (exec) { + i = strlen (exec) + 6; + mexec = xmalloc (sizeof (char) * i); + snprintf (mexec, i, "exec=%s", exec); + } else + mexec = xstrdup ("exec="); + + if (name) { + i = strlen (name) + 6; + mname = xmalloc (sizeof (char) * i); + snprintf (mname, i, "name=%s", name); + } else + mname = xstrdup ("name="); + + if (pidfile) { + i = strlen (pidfile) + 9; + mpidfile = xmalloc (sizeof (char) * i); + snprintf (mpidfile, i, "pidfile=%s", pidfile); + } else + mpidfile = xstrdup ("pidfile="); + + /* Regardless, erase any existing daemon info */ + if ((dp = opendir (dirpath))) { + while ((d = readdir (dp))) { + if (d->d_name[0] == '.') + continue; + file = rc_strcatpaths (dirpath, d->d_name, (char *) NULL); + nfiles++; + + if (! oldfile) { + if (_match_daemon (dirpath, d->d_name, + mexec, mname, mpidfile)) + { + unlink (file); + oldfile = file; + nfiles--; + } + } else { + rename (file, oldfile); + free (oldfile); + oldfile = file; + } + } + free (file); + closedir (dp); + } + + /* Now store our daemon info */ + if (started) { + char buffer[10]; + FILE *fp; + + if (mkdir (dirpath, 0755) == 0 || errno == EEXIST) { + snprintf (buffer, sizeof (buffer), "%03d", nfiles + 1); + file = rc_strcatpaths (dirpath, buffer, (char *) NULL); + if ((fp = fopen (file, "w"))) { + fprintf (fp, "%s\n%s\n%s\n", mexec, mname, mpidfile); + fclose (fp); + retval = true; + } + free (file); + } + } else + retval = true; + + free (mexec); + free (mname); + free (mpidfile); + free (dirpath); + + return (retval); +} +librc_hidden_def(rc_service_daemon_set) + +bool rc_service_started_daemon (const char *service, const char *exec, + int indx) +{ + char *dirpath; + char *file; + int i; + char *mexec; + bool retval = false; + DIR *dp; + struct dirent *d; + + if (! service || ! exec) + return (false); + + dirpath = rc_strcatpaths (RC_SVCDIR, "daemons", basename_c (service), + (char *) NULL); + + i = strlen (exec) + 6; + mexec = xmalloc (sizeof (char) * i); + snprintf (mexec, i, "exec=%s", exec); + + if (indx > 0) { + int len = sizeof (char) * 10; + file = xmalloc (len); + snprintf (file, len, "%03d", indx); + retval = _match_daemon (dirpath, file, mexec, NULL, NULL); + free (file); + } else { + if ((dp = opendir (dirpath))) { + while ((d = readdir (dp))) { + if (d->d_name[0] == ',') + continue; + retval = _match_daemon (dirpath, d->d_name, mexec, NULL, NULL); + if (retval) + break; + } + closedir (dp); + } + } + + free (dirpath); + free (mexec); + return (retval); +} +librc_hidden_def(rc_service_started_daemon) + +bool rc_service_daemons_crashed (const char *service) +{ + char *dirpath; + DIR *dp; + struct dirent *d; + char *path; + FILE *fp; + char *buffer = NULL; + char *exec = NULL; + char *name = NULL; + char *pidfile = NULL; + pid_t pid = 0; + pid_t *pids = NULL; + char *p; + char *token; + bool retval = false; + + if (! service) + return (false); + + dirpath = rc_strcatpaths (RC_SVCDIR, "daemons", basename_c (service), + (char *) NULL); + + if (! (dp = opendir (dirpath))) { + free (dirpath); + return (false); + } + + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + memset (buffer, 0, RC_LINEBUFFER); + + while ((d = readdir (dp))) { + if (d->d_name[0] == '.') + continue; + + path = rc_strcatpaths (dirpath, d->d_name, (char *) NULL); + fp = fopen (path, "r"); + free (path); + if (! fp) + break; + + while ((fgets (buffer, RC_LINEBUFFER, fp))) { + int lb = strlen (buffer) - 1; + if (buffer[lb] == '\n') + buffer[lb] = 0; + + p = buffer; + if ((token = strsep (&p, "=")) == NULL || ! p) + continue; + + if (strlen (p) == 0) + continue; + + if (strcmp (token, "exec") == 0) { + if (exec) + free (exec); + exec = xstrdup (p); + } else if (strcmp (token, "name") == 0) { + if (name) + free (name); + name = xstrdup (p); + } else if (strcmp (token, "pidfile") == 0) { + if (pidfile) + free (pidfile); + pidfile = xstrdup (p); + } + } + fclose (fp); + + pid = 0; + if (pidfile) { + if (! exists (pidfile)) { + retval = true; + break; + } + + if ((fp = fopen (pidfile, "r")) == NULL) { + retval = true; + break; + } + + if (fscanf (fp, "%d", &pid) != 1) { + fclose (fp); + retval = true; + break; + } + + fclose (fp); + free (pidfile); + pidfile = NULL; + + /* We have the pid, so no need to match on name */ + free (exec); + exec = NULL; + free (name); + name = NULL; + } + + if ((pids = rc_find_pids (exec, name, 0, pid)) == NULL) { + retval = true; + break; + } + free (pids); + + free (exec); + exec = NULL; + free (name); + name = NULL; + } + + free (buffer); + free (exec); + free (name); + free (dirpath); + closedir (dp); + + return (retval); +} +librc_hidden_def(rc_service_daemons_crashed) diff --git a/src/librc/librc-depend.c b/src/librc/librc-depend.c new file mode 100644 index 00000000..902d012f --- /dev/null +++ b/src/librc/librc-depend.c @@ -0,0 +1,979 @@ +/* + librc-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 "librc.h" + +#define GENDEP RC_LIBDIR "/sh/gendepends.sh" + +#define RC_DEPCONFIG RC_SVCDIR "/depconfig" + +static const char *bootlevel = NULL; + +/* We use this so we can pass our char array through many functions */ +struct lhead +{ + char **list; +}; + +static char *get_shell_value (char *string) +{ + char *p = string; + char *e; + + if (! string) + return (NULL); + + if (*p == '\'') + p++; + + e = p + strlen (p) - 1; + if (*e == '\n') + *e-- = 0; + if (*e == '\'') + *e-- = 0; + + if (*p != 0) + return p; + + return (NULL); +} + +void rc_deptree_free (rc_depinfo_t *deptree) +{ + rc_depinfo_t *di = deptree; + while (di) + { + rc_depinfo_t *dip = di->next; + rc_deptype_t *dt = di->depends; + free (di->service); + while (dt) + { + rc_deptype_t *dtp = dt->next; + free (dt->type); + rc_strlist_free (dt->services); + free (dt); + dt = dtp; + } + free (di); + di = dip; + } +} +librc_hidden_def(rc_deptree_free) + +static rc_depinfo_t *get_depinfo (const rc_depinfo_t *deptree, + const char *service) +{ + const rc_depinfo_t *di; + + if (! deptree || ! service) + return (NULL); + + for (di = deptree; di; di = di->next) + if (strcmp (di->service, service) == 0) + return ((rc_depinfo_t *)di); + + return (NULL); +} + +static rc_deptype_t *get_deptype (const rc_depinfo_t *depinfo, + const char *type) +{ + rc_deptype_t *dt; + + if (! depinfo || !type) + return (NULL); + + for (dt = depinfo->depends; dt; dt = dt->next) + if (strcmp (dt->type, type) == 0) + return (dt); + + return (NULL); +} + +rc_depinfo_t *rc_deptree_load (void) +{ + FILE *fp; + rc_depinfo_t *deptree = NULL; + rc_depinfo_t *depinfo = NULL; + rc_deptype_t *deptype = NULL; + char buffer [RC_LINEBUFFER]; + char *type; + char *p; + char *e; + int i; + + if (! (fp = fopen (RC_DEPTREE, "r"))) + return (NULL); + + while (fgets (buffer, RC_LINEBUFFER, fp)) + { + p = buffer; + e = strsep (&p, "_"); + if (! e || strcmp (e, "depinfo") != 0) + continue; + + e = strsep (&p, "_"); + if (! e || sscanf (e, "%d", &i) != 1) + continue; + + if (! (type = strsep (&p, "_="))) + continue; + + if (strcmp (type, "service") == 0) + { + /* Sanity */ + e = get_shell_value (p); + if (! e || strlen (e) == 0) + continue; + + if (! deptree) + { + deptree = xmalloc (sizeof (rc_depinfo_t)); + depinfo = deptree; + } + else + { + depinfo->next = xmalloc (sizeof (rc_depinfo_t)); + depinfo = depinfo->next; + } + memset (depinfo, 0, sizeof (rc_depinfo_t)); + depinfo->service = xstrdup (e); + deptype = NULL; + continue; + } + + e = strsep (&p, "="); + if (! e || sscanf (e, "%d", &i) != 1) + continue; + + /* Sanity */ + e = get_shell_value (p); + if (! e || strlen (e) == 0) + continue; + + if (! deptype) + { + depinfo->depends = xmalloc (sizeof (rc_deptype_t)); + deptype = depinfo->depends; + memset (deptype, 0, sizeof (rc_deptype_t)); + } + else + if (strcmp (deptype->type, type) != 0) + { + deptype->next = xmalloc (sizeof (rc_deptype_t)); + deptype = deptype->next; + memset (deptype, 0, sizeof (rc_deptype_t)); + } + + if (! deptype->type) + deptype->type = xstrdup (type); + + rc_strlist_addsort (&deptype->services, e); + } + fclose (fp); + + return (deptree); +} +librc_hidden_def(rc_deptree_load) + +static bool valid_service (const char *runlevel, const char *service) +{ + rc_service_state_t state = rc_service_state (service); + + return ((strcmp (runlevel, bootlevel) != 0 && + rc_service_in_runlevel (service, bootlevel)) || + rc_service_in_runlevel (service, runlevel) || + state & RC_SERVICE_COLDPLUGGED || + state & RC_SERVICE_STARTED); +} + +static bool get_provided1 (const char *runlevel, struct lhead *providers, + rc_deptype_t *deptype, + const char *level, bool coldplugged, + rc_service_state_t state) +{ + char *service; + int i; + bool retval = false; + + STRLIST_FOREACH (deptype->services, service, i) + { + bool ok = true; + rc_service_state_t s = rc_service_state (service); + if (level) + ok = rc_service_in_runlevel (service, level); + else if (coldplugged) + ok = (s & RC_SERVICE_COLDPLUGGED && + ! rc_service_in_runlevel (service, runlevel) && + ! rc_service_in_runlevel (service, bootlevel)); + + if (! ok) + continue; + + switch (state) { + case RC_SERVICE_STARTED: + ok = (s & RC_SERVICE_STARTED); + break; + case RC_SERVICE_INACTIVE: + case RC_SERVICE_STARTING: + case RC_SERVICE_STOPPING: + ok = (s & RC_SERVICE_STARTING || + s & RC_SERVICE_STOPPING || + s & RC_SERVICE_INACTIVE); + break; + default: + break; + } + + if (! ok) + continue; + + retval = true; + rc_strlist_add (&providers->list, service); + } + + return (retval); +} + +/* Work out if a service is provided by another service. + For example metalog provides logger. + We need to be able to handle syslogd providing logger too. + We do this by checking whats running, then what's starting/stopping, + then what's run in the runlevels and finally alphabetical order. + + If there are any bugs in rc-depend, they will probably be here as + provided dependancy can change depending on runlevel state. + */ +static char **get_provided (const rc_depinfo_t *deptree, + const rc_depinfo_t *depinfo, + const char *runlevel, int options) +{ + rc_deptype_t *dt; + struct lhead providers; + char *service; + int i; + + if (! deptree || ! depinfo) + return (NULL); + if (rc_service_exists (depinfo->service)) + return (NULL); + + dt = get_deptype (depinfo, "providedby"); + if (! dt) + return (NULL); + + memset (&providers, 0, sizeof (struct lhead)); + /* If we are stopping then all depends are true, regardless of state. + This is especially true for net services as they could force a restart + of the local dns resolver which may depend on net. */ + if (options & RC_DEP_STOP) + { + STRLIST_FOREACH (dt->services, service, i) + rc_strlist_add (&providers.list, service); + + return (providers.list); + } + + /* If we're strict, then only use what we have in our runlevel + * and bootlevel */ + if (options & RC_DEP_STRICT) + { + STRLIST_FOREACH (dt->services, service, i) + if (rc_service_in_runlevel (service, runlevel) || + rc_service_in_runlevel (service, bootlevel)) + rc_strlist_add (&providers.list, service); + + if (providers.list) + return (providers.list); + } + + /* OK, we're not strict or there were no services in our runlevel. + This is now where the logic gets a little fuzzy :) + If there is >1 running service then we return NULL. + We do this so we don't hang around waiting for inactive services and + our need has already been satisfied as it's not strict. + We apply this to our runlevel, coldplugged services, then bootlevel + and finally any running.*/ +#define DO \ + if (providers.list && providers.list[0] && providers.list[1]) \ + { \ + rc_strlist_free (providers.list); \ + return (NULL); \ + } \ + else if (providers.list) \ + return providers.list; \ + + /* Anything in the runlevel has to come first */ + if (get_provided1 (runlevel, &providers, dt, runlevel, false, RC_SERVICE_STARTED)) + { DO } + if (get_provided1 (runlevel, &providers, dt, runlevel, false, RC_SERVICE_STARTING)) + return (providers.list); + if (get_provided1 (runlevel, &providers, dt, runlevel, false, RC_SERVICE_STOPPED)) + return (providers.list); + + /* Check coldplugged services */ + if (get_provided1 (runlevel, &providers, dt, NULL, true, RC_SERVICE_STARTED)) + { DO } + if (get_provided1 (runlevel, &providers, dt, NULL, true, RC_SERVICE_STARTING)) + return (providers.list); + + /* Check bootlevel if we're not in it */ + if (bootlevel && strcmp (runlevel, bootlevel) != 0) + { + if (get_provided1 (runlevel, &providers, dt, bootlevel, false, RC_SERVICE_STARTED)) + { DO } + if (get_provided1 (runlevel, &providers, dt, bootlevel, false, RC_SERVICE_STARTING)) + return (providers.list); + } + + /* Check coldplugged services */ + if (get_provided1 (runlevel, &providers, dt, NULL, true, RC_SERVICE_STOPPED)) + { DO } + + /* Check manually started */ + if (get_provided1 (runlevel, &providers, dt, NULL, false, RC_SERVICE_STARTED)) + { DO } + if (get_provided1 (runlevel, &providers, dt, NULL, false, RC_SERVICE_STARTING)) + return (providers.list); + + /* Nothing started then. OK, lets get the stopped services */ + if (get_provided1 (runlevel, &providers, dt, runlevel, false, RC_SERVICE_STOPPED)) + return (providers.list); + + if (bootlevel && (strcmp (runlevel, bootlevel) != 0) + && (get_provided1 (runlevel, &providers, dt, bootlevel, false, RC_SERVICE_STOPPED))) + return (providers.list); + + /* Still nothing? OK, list all services */ + STRLIST_FOREACH (dt->services, service, i) + rc_strlist_add (&providers.list, service); + + return (providers.list); +} + +static void visit_service (const rc_depinfo_t *deptree, + const char * const *types, + struct lhead *sorted, struct lhead *visited, + const rc_depinfo_t *depinfo, + const char *runlevel, int options) +{ + int i, j, k; + char *lp; + const char *item; + char *service; + rc_depinfo_t *di; + rc_deptype_t *dt; + char **provides; + char *svcname; + + if (! deptree || !sorted || !visited || !depinfo) + return; + + /* Check if we have already visited this service or not */ + STRLIST_FOREACH (visited->list, item, i) + if (strcmp (item, depinfo->service) == 0) + return; + + /* Add ourselves as a visited service */ + rc_strlist_add (&visited->list, depinfo->service); + + STRLIST_FOREACH (types, item, i) + { + if ((dt = get_deptype (depinfo, item))) + { + STRLIST_FOREACH (dt->services, service, j) + { + if (! options & RC_DEP_TRACE || strcmp (item, "iprovide") == 0) + { + rc_strlist_add (&sorted->list, service); + continue; + } + + di = get_depinfo (deptree, service); + if ((provides = get_provided (deptree, di, runlevel, options))) + { + STRLIST_FOREACH (provides, lp, k) + { + di = get_depinfo (deptree, lp); + if (di && (strcmp (item, "ineed") == 0 || + strcmp (item, "needsme") == 0 || + valid_service (runlevel, di->service))) + visit_service (deptree, types, sorted, visited, di, + runlevel, options | RC_DEP_TRACE); + } + rc_strlist_free (provides); + } + else + if (di && (strcmp (item, "ineed") == 0 || + strcmp (item, "needsme") == 0 || + valid_service (runlevel, service))) + visit_service (deptree, types, sorted, visited, di, + runlevel, options | RC_DEP_TRACE); + } + } + } + + /* Now visit the stuff we provide for */ + if (options & RC_DEP_TRACE && + (dt = get_deptype (depinfo, "iprovide"))) + { + STRLIST_FOREACH (dt->services, service, i) + { + if ((di = get_depinfo (deptree, service))) + if ((provides = get_provided (deptree, di, runlevel, options))) + { + STRLIST_FOREACH (provides, lp, j) + if (strcmp (lp, depinfo->service) == 0) + { + visit_service (deptree, types, sorted, visited, di, + runlevel, options | RC_DEP_TRACE); + break; + } + rc_strlist_free (provides); + } + } + } + + /* We've visited everything we need, so add ourselves unless we + are also the service calling us or we are provided by something */ + svcname = getenv("SVCNAME"); + if (! svcname || strcmp (svcname, depinfo->service) != 0) + if (! get_deptype (depinfo, "providedby")) + rc_strlist_add (&sorted->list, depinfo->service); +} + +char **rc_deptree_depend (const rc_depinfo_t *deptree, + const char *service, const char *type) +{ + rc_depinfo_t *di; + rc_deptype_t *dt; + char **svcs = NULL; + int i; + char *svc; + + if (! (di = get_depinfo (deptree, service)) || + ! (dt = get_deptype (di, type))) + { + errno = ENOENT; + return (NULL); + } + + /* For consistency, we copy the array */ + STRLIST_FOREACH (dt->services, svc, i) + rc_strlist_add (&svcs, svc); + + return (svcs); +} +librc_hidden_def(rc_deptree_depend) + +char **rc_deptree_depends (const rc_depinfo_t *deptree, + const char *const *types, + const char *const *services, + const char *runlevel, int options) +{ + struct lhead sorted; + struct lhead visited; + rc_depinfo_t *di; + const char *service; + int i; + + if (! deptree || ! services) + return (NULL); + + memset (&sorted, 0, sizeof (struct lhead)); + memset (&visited, 0, sizeof (struct lhead)); + + bootlevel = getenv ("RC_BOOTLEVEL"); + if (! bootlevel) + bootlevel = RC_LEVEL_BOOT; + + STRLIST_FOREACH (services, service, i) + { + if (! (di = get_depinfo (deptree, service))) { + errno = ENOENT; + continue; + } + if (types) + visit_service (deptree, types, &sorted, &visited, + di, runlevel, options); + } + + rc_strlist_free (visited.list); + return (sorted.list); +} +librc_hidden_def(rc_deptree_depends) + +static const char * const order_types[] = { "ineed", "iuse", "iafter", NULL }; +char **rc_deptree_order (const rc_depinfo_t *deptree, const char *runlevel, + int options) +{ + char **list = NULL; + char **services = NULL; + bool reverse = false; + char **tmp = NULL; + + if (! runlevel) + return (NULL); + + bootlevel = getenv ("RC_BOOTLEVEL"); + if (! bootlevel) + bootlevel = RC_LEVEL_BOOT; + + /* When shutting down, list all running services */ + if (strcmp (runlevel, RC_LEVEL_SINGLE) == 0 || + strcmp (runlevel, RC_LEVEL_SHUTDOWN) == 0 || + strcmp (runlevel, RC_LEVEL_REBOOT) == 0) + { + list = rc_services_in_state (RC_SERVICE_STARTED); + + tmp = rc_services_in_state (RC_SERVICE_INACTIVE); + rc_strlist_join (&list, tmp); + rc_strlist_free (tmp); + + tmp = rc_services_in_state (RC_SERVICE_STARTING); + rc_strlist_join (&list, tmp); + rc_strlist_free (tmp); + reverse = true; + } else { + list = rc_services_in_runlevel (runlevel); + + /* Add coldplugged services */ + tmp = rc_services_in_state (RC_SERVICE_COLDPLUGGED); + rc_strlist_join (&list, tmp); + rc_strlist_free (tmp); + + /* If we're not the boot runlevel then add that too */ + if (strcmp (runlevel, bootlevel) != 0) { + tmp = rc_services_in_runlevel (bootlevel); + rc_strlist_join (&list, tmp); + rc_strlist_free (tmp); + } + } + + /* Now we have our lists, we need to pull in any dependencies + and order them */ + services = rc_deptree_depends (deptree, order_types, (const char **) list, + runlevel, + RC_DEP_STRICT | RC_DEP_TRACE | options); + rc_strlist_free (list); + + if (reverse) + rc_strlist_reverse (services); + + return (services); +} +librc_hidden_def(rc_deptree_order) + +static bool is_newer_than (const char *file, const char *target) +{ + struct stat buf; + time_t mtime; + bool newer = true; + DIR *dp; + struct dirent *d; + char *path; + + if (stat (file, &buf) != 0 || buf.st_size == 0) + return (false); + mtime = buf.st_mtime; + + /* Of course we are newever than targets that don't exist + Such as broken symlinks */ + if (stat (target, &buf) != 0) + return (true); + + if (mtime < buf.st_mtime) + return (false); + + if (! (dp = opendir (target))) + return (true); + + while ((d = readdir (dp))) { + if (d->d_name[0] == '.') + continue; + + path = rc_strcatpaths (target, d->d_name, (char *) NULL); + newer = is_newer_than (file, path); + free (path); + if (! newer) + break; + } + closedir (dp); + + return (newer); +} + +typedef struct deppair +{ + const char *depend; + const char *addto; +} deppair_t; + +static const deppair_t deppairs[] = { + { "ineed", "needsme" }, + { "iuse", "usesme" }, + { "iafter", "ibefore" }, + { "ibefore", "iafter" }, + { "iprovide", "providedby" }, + { NULL, NULL } +}; + +static const char *depdirs[] = +{ + RC_SVCDIR "/starting", + RC_SVCDIR "/started", + RC_SVCDIR "/stopping", + RC_SVCDIR "/inactive", + RC_SVCDIR "/wasinactive", + RC_SVCDIR "/failed", + RC_SVCDIR "/coldplugged", + RC_SVCDIR "/daemons", + RC_SVCDIR "/options", + RC_SVCDIR "/exclusive", + RC_SVCDIR "/scheduled", + NULL +}; + +bool rc_deptree_update_needed (void) +{ + bool newer = false; + char **config; + char *service; + int i; + + /* Create base directories if needed */ + for (i = 0; depdirs[i]; i++) + if (mkdir (depdirs[i], 0755) != 0 && errno != EEXIST) + fprintf (stderr, "mkdir `%s': %s\n", depdirs[i], strerror (errno)); + + /* Quick test to see if anything we use has changed */ + if (! is_newer_than (RC_DEPTREE, RC_INITDIR) || + ! is_newer_than (RC_DEPTREE, RC_CONFDIR) || + ! is_newer_than (RC_DEPTREE, RC_INITDIR_LOCAL) || + ! is_newer_than (RC_DEPTREE, RC_CONFDIR_LOCAL) || + ! is_newer_than (RC_DEPTREE, "/etc/rc.conf")) + return (true); + + /* Some init scripts dependencies change depending on config files + * outside of baselayout, like syslog-ng, so we check those too. */ + config = rc_config_list (RC_DEPCONFIG); + STRLIST_FOREACH (config, service, i) { + if (! is_newer_than (RC_DEPTREE, service)) { + newer = true; + break; + } + } + rc_strlist_free (config); + + return (newer); +} +librc_hidden_def(rc_deptree_update_needed) + +/* This is a 5 phase operation + Phase 1 is a shell script which loads each init script and config in turn + and echos their dependency info to stdout + Phase 2 takes that and populates a depinfo object with that data + Phase 3 adds any provided services to the depinfo object + Phase 4 scans that depinfo object and puts in backlinks + Phase 5 saves the depinfo object to disk + */ +bool rc_deptree_update (void) +{ + char *depends; + char *service; + char *type; + char *depend; + char **config = NULL; + int retval = true; + FILE *fp; + rc_depinfo_t *deptree; + rc_depinfo_t *depinfo; + rc_depinfo_t *di; + rc_depinfo_t *last_depinfo = NULL; + rc_deptype_t *deptype = NULL; + rc_deptype_t *dt; + rc_deptype_t *last_deptype = NULL; + char *buffer = NULL; + int len; + int i; + int j; + int k; + bool already_added; + + /* Some init scripts need RC_LIBDIR to source stuff + Ideally we should be setting our full env instead */ + if (! getenv ("RC_LIBDIR")) + setenv ("RC_LIBDIR", RC_LIBDIR, 0); + + /* Phase 1 */ + if (! (fp = popen (GENDEP, "r"))) + return (false); + + deptree = xmalloc (sizeof (rc_depinfo_t)); + memset (deptree, 0, sizeof (rc_depinfo_t)); + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + memset (buffer, 0, RC_LINEBUFFER); + + /* Phase 2 */ + while (fgets (buffer, RC_LINEBUFFER, fp)) + { + /* Trim the newline */ + if (buffer[strlen (buffer) - 1] == '\n') + buffer[strlen(buffer) -1] = 0; + + depends = buffer; + service = strsep (&depends, " "); + if (! service) + continue; + type = strsep (&depends, " "); + + for (depinfo = deptree; depinfo; depinfo = depinfo->next) + { + last_depinfo = depinfo; + if (depinfo->service && strcmp (depinfo->service, service) == 0) + break; + } + + if (! depinfo) + { + if (! last_depinfo->service) + depinfo = last_depinfo; + else + { + last_depinfo->next = xmalloc (sizeof (rc_depinfo_t)); + depinfo = last_depinfo->next; + } + memset (depinfo, 0, sizeof (rc_depinfo_t)); + depinfo->service = xstrdup (service); + } + + /* We may not have any depends */ + if (! type || ! depends) + continue; + + /* Get the type */ + if (strcmp (type, "config") != 0) { + last_deptype = NULL; + for (deptype = depinfo->depends; deptype; deptype = deptype->next) + { + last_deptype = deptype; + if (strcmp (deptype->type, type) == 0) + break; + } + + if (! deptype) + { + if (! last_deptype) + { + depinfo->depends = xmalloc (sizeof (rc_deptype_t)); + deptype = depinfo->depends; + } + else + { + last_deptype->next = xmalloc (sizeof (rc_deptype_t)); + deptype = last_deptype->next; + } + memset (deptype, 0, sizeof (rc_deptype_t)); + deptype->type = xstrdup (type); + } + } + + /* Now add each depend to our type. + We do this individually so we handle multiple spaces gracefully */ + while ((depend = strsep (&depends, " "))) + { + if (depend[0] == 0) + continue; + + if (strcmp (type, "config") == 0) { + rc_strlist_addsort (&config, depend); + continue; + } + + /* .sh files are not init scripts */ + len = strlen (depend); + if (len > 2 && + depend[len - 3] == '.' && + depend[len - 2] == 's' && + depend[len - 1] == 'h') + continue; + + rc_strlist_addsort (&deptype->services, depend); + + /* We need to allow `after *; before local;` to work. + * Conversely, we need to allow 'before *; after modules' also */ + /* If we're before something, remove us from the after list */ + if (strcmp (type, "ibefore") == 0) { + if ((dt = get_deptype (depinfo, "iafter"))) + rc_strlist_delete (&dt->services, depend); + } + /* If we're after something, remove us from the before list */ + if (strcmp (type, "iafter") == 0 || + strcmp (type, "ineed") == 0 || + strcmp (type, "iuse") == 0) { + if ((dt = get_deptype (depinfo, "ibefore"))) + rc_strlist_delete (&dt->services, depend); + } + } + } + pclose (fp); + free (buffer); + + /* Phase 3 - add our providors to the tree */ + for (depinfo = deptree; depinfo; depinfo = depinfo->next) + { + if ((deptype = get_deptype (depinfo, "iprovide"))) + STRLIST_FOREACH (deptype->services, service, i) + { + for (di = deptree; di; di = di->next) + { + last_depinfo = di; + if (strcmp (di->service, service) == 0) + break; + } + if (! di) + { + last_depinfo->next = xmalloc (sizeof (rc_depinfo_t)); + di = last_depinfo->next; + memset (di, 0, sizeof (rc_depinfo_t)); + di->service = xstrdup (service); + } + } + } + + /* Phase 4 - backreference our depends */ + for (depinfo = deptree; depinfo; depinfo = depinfo->next) + { + for (i = 0; deppairs[i].depend; i++) + { + deptype = get_deptype (depinfo, deppairs[i].depend); + if (! deptype) + continue; + + STRLIST_FOREACH (deptype->services, service, j) + { + di = get_depinfo (deptree, service); + if (! di) + { + if (strcmp (deptype->type, "ineed") == 0) + { + fprintf (stderr, + "Service `%s' needs non existant service `%s'\n", + depinfo->service, service); + } + continue; + } + + /* Add our deptype now */ + last_deptype = NULL; + for (dt = di->depends; dt; dt = dt->next) + { + last_deptype = dt; + if (strcmp (dt->type, deppairs[i].addto) == 0) + break; + } + if (! dt) + { + if (! last_deptype) + { + di->depends = xmalloc (sizeof (rc_deptype_t)); + dt = di->depends; + } + else + { + last_deptype->next = xmalloc (sizeof (rc_deptype_t)); + dt = last_deptype->next; + } + memset (dt, 0, sizeof (rc_deptype_t)); + dt->type = xstrdup (deppairs[i].addto); + } + + already_added = false; + STRLIST_FOREACH (dt->services, service, k) + if (strcmp (service, depinfo->service) == 0) + { + already_added = true; + break; + } + + if (! already_added) + rc_strlist_addsort (&dt->services, depinfo->service); + } + } + } + + /* Phase 5 - save to disk + Now that we're purely in C, do we need to keep a shell parseable file? + I think yes as then it stays human readable + This works and should be entirely shell parseable provided that depend + names don't have any non shell variable characters in + */ + if ((fp = fopen (RC_DEPTREE, "w"))) { + i = 0; + for (depinfo = deptree; depinfo; depinfo = depinfo->next) + { + fprintf (fp, "depinfo_%d_service='%s'\n", i, depinfo->service); + for (deptype = depinfo->depends; deptype; deptype = deptype->next) + { + k = 0; + STRLIST_FOREACH (deptype->services, service, j) + { + fprintf (fp, "depinfo_%d_%s_%d='%s'\n", i, deptype->type, + k, service); + k++; + } + } + i++; + } + fclose (fp); + } else { + fprintf (stderr, "fopen `%s': %s\n", RC_DEPTREE, strerror (errno)); + retval = false; + } + + /* Save our external config files to disk */ + if (config) { + if ((fp = fopen (RC_DEPCONFIG, "w"))) { + STRLIST_FOREACH (config, service, i) + fprintf (fp, "%s\n", service); + fclose (fp); + } else { + fprintf (stderr, "fopen `%s': %s\n", RC_DEPCONFIG, strerror (errno)); + retval = false; + } + rc_strlist_free (config); + } + + rc_deptree_free (deptree); + + return (retval); +} +librc_hidden_def(rc_deptree_update) diff --git a/src/librc/librc-depend.h b/src/librc/librc-depend.h new file mode 100644 index 00000000..238f70d1 --- /dev/null +++ b/src/librc/librc-depend.h @@ -0,0 +1,61 @@ +/* + * librc-depend.h + * Internal header file for dependency structures + */ + +/* + * 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_DEPEND_H +#define _LIBRC_DEPEND_H + +/*! @name Dependency structures + * private to librc - rc.h exposes them just a pointers */ + +/*! Singly linked list of dependency types that list the services the + * type is for */ +typedef struct rc_deptype +{ + /*! ineed, iuse, iafter, etc */ + char *type; + /*! NULL terminated list of services */ + char **services; + /*! Next dependency type */ + struct rc_deptype *next; +} rc_deptype_t; + +/*! Singly linked list of services and their dependencies */ +typedef struct rc_depinfo +{ + /*! Name of service */ + char *service; + /*! Dependencies */ + rc_deptype_t *depends; + /*! Next service dependency type */ + struct rc_depinfo *next; +} rc_depinfo_t; + +#endif diff --git a/src/librc/librc-misc.c b/src/librc/librc-misc.c new file mode 100644 index 00000000..dcecc293 --- /dev/null +++ b/src/librc/librc-misc.c @@ -0,0 +1,245 @@ +/* + rc-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 "librc.h" + +bool rc_yesno (const char *value) +{ + if (! value) { + errno = ENOENT; + return (false); + } + + if (strcasecmp (value, "yes") == 0 || + strcasecmp (value, "y") == 0 || + strcasecmp (value, "true") == 0 || + strcasecmp (value, "1") == 0) + return (true); + + if (strcasecmp (value, "no") != 0 && + strcasecmp (value, "n") != 0 && + strcasecmp (value, "false") != 0 && + strcasecmp (value, "0") != 0) + errno = EINVAL; + + return (false); +} +librc_hidden_def(rc_yesno) + +char *rc_strcatpaths (const char *path1, const char *paths, ...) +{ + va_list ap; + int length; + int i; + char *p; + char *path; + char *pathp; + + if (! path1 || ! paths) + return (NULL); + + length = strlen (path1) + strlen (paths) + 1; + if (*paths != '/') + length ++; + + va_start (ap, paths); + while ((p = va_arg (ap, char *)) != NULL) { + if (*p != '/') + length ++; + length += strlen (p); + } + va_end (ap); + + pathp = path = xmalloc (length * sizeof (char)); + memset (path, 0, length); + i = strlen (path1); + memcpy (path, path1, i); + pathp += i; + if (*paths != '/') + *pathp ++ = '/'; + i = strlen (paths); + memcpy (pathp, paths, i); + pathp += i; + + va_start (ap, paths); + while ((p = va_arg (ap, char *)) != NULL) { + if (*p != '/') + *pathp ++= '/'; + i = strlen (p); + memcpy (pathp, p, i); + pathp += i; + } + va_end (ap); + + *pathp++ = 0; + + return (path); +} +librc_hidden_def(rc_strcatpaths) + + +char **rc_config_load (const char *file) +{ + char **list = NULL; + FILE *fp; + char *buffer; + char *p; + char *token; + char *line; + char *linep; + char *linetok; + int i = 0; + bool replaced; + char *entry; + char *newline; + + if (! (fp = fopen (file, "r"))) + return (NULL); + + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + while (fgets (buffer, RC_LINEBUFFER, fp)) { + p = buffer; + + /* Strip leading spaces/tabs */ + while ((*p == ' ') || (*p == '\t')) + p++; + + if (! p || strlen (p) < 3 || p[0] == '#') + continue; + + /* Get entry */ + token = strsep (&p, "="); + + if (! token) + continue; + + entry = xstrdup (token); + + /* Preserve shell coloring */ + if (*p == '$') + token = p; + else + do { + /* Bash variables are usually quoted */ + token = strsep (&p, "\"\'"); + } while ((token) && (strlen (token) == 0)); + + /* Drop a newline if that's all we have */ + i = strlen (token) - 1; + if (token[i] == 10) + token[i] = 0; + + i = strlen (entry) + strlen (token) + 2; + newline = xmalloc (i); + snprintf (newline, i, "%s=%s", entry, token); + + replaced = false; + /* In shells the last item takes precedence, so we need to remove + any prior values we may already have */ + STRLIST_FOREACH (list, line, i) { + char *tmp = xstrdup (line); + linep = tmp; + linetok = strsep (&linep, "="); + if (strcmp (linetok, entry) == 0) { + /* We have a match now - to save time we directly replace it */ + free (list[i - 1]); + list[i - 1] = newline; + replaced = true; + free (tmp); + break; + } + free (tmp); + } + + if (! replaced) { + rc_strlist_addsort (&list, newline); + free (newline); + } + free (entry); + } + free (buffer); + fclose (fp); + + return (list); +} +librc_hidden_def(rc_config_load) + +char *rc_config_value (char **list, const char *entry) +{ + char *line; + int i; + char *p; + + STRLIST_FOREACH (list, line, i) { + p = strchr (line, '='); + if (p && strncmp (entry, line, p - line) == 0) + return (p += 1); + } + + return (NULL); +} +librc_hidden_def(rc_config_value) + +char **rc_config_list (const char *file) +{ + FILE *fp; + char *buffer; + char *p; + char *token; + char **list = NULL; + + if (! (fp = fopen (file, "r"))) + return (NULL); + + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + while (fgets (buffer, RC_LINEBUFFER, fp)) { + p = buffer; + + /* Strip leading spaces/tabs */ + while ((*p == ' ') || (*p == '\t')) + p++; + + /* Get entry - we do not want comments */ + token = strsep (&p, "#"); + if (token && (strlen (token) > 1)) { + /* Stip the newline if present */ + if (token[strlen (token) - 1] == '\n') + token[strlen (token) - 1] = 0; + + rc_strlist_add (&list, token); + } + } + free (buffer); + fclose (fp); + + return (list); +} +librc_hidden_def(rc_config_list) diff --git a/src/librc/librc-strlist.c b/src/librc/librc-strlist.c new file mode 100644 index 00000000..815c8370 --- /dev/null +++ b/src/librc/librc-strlist.c @@ -0,0 +1,230 @@ +/* + librc-strlist.h + String list functions for using char ** arrays + + Based on a previous implementation by Martin Schlemmer + */ + +/* + * 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 "librc.h" + +static char *_rc_strlist_add (char ***list, const char *item, bool uniq) +{ + char **newlist; + char **lst = *list; + int i = 0; + + if (! item) + return (NULL); + + while (lst && lst[i]) { + if (uniq && strcmp (lst[i], item) == 0) { + errno = EEXIST; + return (NULL); + } + i++; + } + + newlist = xrealloc (lst, sizeof (char *) * (i + 2)); + newlist[i] = xstrdup (item); + newlist[i + 1] = NULL; + + *list = newlist; + return (newlist[i]); +} + +char *rc_strlist_add (char ***list, const char *item) +{ + return (_rc_strlist_add (list, item, false)); +} +librc_hidden_def(rc_strlist_add) + +char *rc_strlist_addu (char ***list, const char *item) +{ + return (_rc_strlist_add (list, item, true)); +} +librc_hidden_def(rc_strlist_addu) + +static char *_rc_strlist_addsort (char ***list, const char *item, + int (*sortfunc) (const char *s1, + const char *s2), + bool uniq) +{ + char **newlist; + char **lst = *list; + int i = 0; + char *tmp1; + char *tmp2; + char *retval; + + if (! item) + return (NULL); + + while (lst && lst[i]) { + if (uniq && strcmp (lst[i], item) == 0) { + errno = EEXIST; + return (NULL); + } + i++; + } + + newlist = xrealloc (lst, sizeof (char *) * (i + 2)); + + if (! i) + newlist[i] = NULL; + newlist[i + 1] = NULL; + + i = 0; + while (newlist[i] && sortfunc (newlist[i], item) < 0) + i++; + + tmp1 = newlist[i]; + retval = newlist[i] = xstrdup (item); + do { + i++; + tmp2 = newlist[i]; + newlist[i] = tmp1; + tmp1 = tmp2; + } while (tmp1); + + *list = newlist; + return (retval); +} + +char *rc_strlist_addsort (char ***list, const char *item) +{ + return (_rc_strlist_addsort (list, item, strcoll, false)); +} +librc_hidden_def(rc_strlist_addsort) + +char *rc_strlist_addsortc (char ***list, const char *item) +{ + return (_rc_strlist_addsort (list, item, strcmp, false)); +} +librc_hidden_def(rc_strlist_addsortc) + +char *rc_strlist_addsortu (char ***list, const char *item) +{ + return (_rc_strlist_addsort (list, item, strcmp, true)); +} +librc_hidden_def(rc_strlist_addsortu) + +bool rc_strlist_delete (char ***list, const char *item) +{ + char **lst = *list; + int i = 0; + + if (!lst || ! item) + return (false); + + while (lst[i]) { + if (strcmp (lst[i], item) == 0) { + free (lst[i]); + do { + lst[i] = lst[i + 1]; + i++; + } while (lst[i]); + return (true); + } + i++; + } + + errno = ENOENT; + return (false); +} +librc_hidden_def(rc_strlist_delete) + +char *rc_strlist_join (char ***list1, char **list2) +{ + char **lst1 = *list1; + char **newlist; + int i = 0; + int j = 0; + + if (! list2) + return (NULL); + + while (lst1 && lst1[i]) + i++; + + while (list2[j]) + j++; + + newlist = xrealloc (lst1, sizeof (char *) * (i + j + 1)); + + j = 0; + while (list2[j]) { + newlist[i] = list2[j]; + /* Take the item off the 2nd list as it's only a shallow copy */ + list2[j] = NULL; + i++; + j++; + } + newlist[i] = NULL; + + *list1 = newlist; + return (newlist[i == 0 ? 0 : i - 1]); +} +librc_hidden_def(rc_strlist_join) + +void rc_strlist_reverse (char **list) +{ + char *item; + int i = 0; + int j = 0; + + if (! list) + return; + + while (list[j]) + j++; + j--; + + while (i < j && list[i] && list[j]) { + item = list[i]; + list[i] = list[j]; + list[j] = item; + i++; + j--; + } +} +librc_hidden_def(rc_strlist_reverse) + +void rc_strlist_free (char **list) +{ + int i = 0; + + if (! list) + return; + + while (list[i]) + free (list[i++]); + + free (list); +} +librc_hidden_def(rc_strlist_free) diff --git a/src/librc/librc.c b/src/librc/librc.c new file mode 100644 index 00000000..15309f87 --- /dev/null +++ b/src/librc/librc.c @@ -0,0 +1,891 @@ +/* + librc + core RC functions + */ + +/* + * Copyright 2007-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +const char librc_copyright[] = "Copyright (c) 2007-2008 Roy Marples"; + +#include "librc.h" + +#define SOFTLEVEL RC_SVCDIR "/softlevel" + +#ifndef S_IXUGO +# define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH) +#endif + +/* File stream used for plugins to write environ vars to */ +FILE *rc_environ_fd = NULL; + +typedef struct rc_service_state_name { + rc_service_state_t state; + const char *name; +} rc_service_state_name_t; + +/* We MUST list the states below 0x10 first + * The rest can be in any order */ +static const rc_service_state_name_t rc_service_state_names[] = { + { RC_SERVICE_STARTED, "started" }, + { RC_SERVICE_STOPPED, "stopped" }, + { RC_SERVICE_STARTING, "starting" }, + { RC_SERVICE_STOPPING, "stopping" }, + { RC_SERVICE_INACTIVE, "inactive" }, + { RC_SERVICE_WASINACTIVE, "wasinactive" }, + { RC_SERVICE_COLDPLUGGED, "coldplugged" }, + { RC_SERVICE_FAILED, "failed" }, + { RC_SERVICE_SCHEDULED, "scheduled"}, + { 0, NULL} +}; + +#define LS_INITD 0x01 +#define LS_DIR 0x02 +static char **ls_dir (const char *dir, int options) +{ + DIR *dp; + struct dirent *d; + char **list = NULL; + struct stat buf; + + if ((dp = opendir (dir)) == NULL) + return (NULL); + + while (((d = readdir (dp)) != NULL)) { + if (d->d_name[0] != '.') { + if (options & LS_INITD) { + int l = strlen (d->d_name); + + /* Check that our file really exists. + * This is important as a service maybe in a runlevel, but + * could also have been removed. */ + char *file = rc_strcatpaths (dir, d->d_name, NULL); + int ok = stat (file, &buf); + free (file); + if (ok != 0) + continue; + + /* .sh files are not init scripts */ + if (l > 2 && d->d_name[l - 3] == '.' && + d->d_name[l - 2] == 's' && + d->d_name[l - 1] == 'h') + continue; + } + if (options & LS_DIR) { + if (stat (d->d_name, &buf) == 0 && ! S_ISDIR (buf.st_mode)) + continue; + } + rc_strlist_addsort (&list, d->d_name); + } + } + closedir (dp); + + return (list); +} + +static bool rm_dir (const char *pathname, bool top) +{ + DIR *dp; + struct dirent *d; + + if ((dp = opendir (pathname)) == NULL) + return (false); + + errno = 0; + while (((d = readdir (dp)) != NULL) && errno == 0) { + if (strcmp (d->d_name, ".") != 0 && strcmp (d->d_name, "..") != 0) { + char *tmp = rc_strcatpaths (pathname, d->d_name, (char *) NULL); + if (d->d_type == DT_DIR) { + if (! rm_dir (tmp, true)) + { + free (tmp); + closedir (dp); + return (false); + } + } else { + if (unlink (tmp)) { + free (tmp); + closedir (dp); + return (false); + } + } + free (tmp); + } + } + closedir (dp); + + if (top && rmdir (pathname) != 0) + return (false); + + return (true); +} + +static const char *rc_parse_service_state (rc_service_state_t state) +{ + int i; + + for (i = 0; rc_service_state_names[i].name; i++) { + if (rc_service_state_names[i].state == state) + return (rc_service_state_names[i].name); + } + + return (NULL); +} + +bool rc_runlevel_starting (void) +{ + return (exists (RC_STARTING)); +} +librc_hidden_def(rc_runlevel_starting) + +bool rc_runlevel_stopping (void) +{ + return (exists (RC_STOPPING)); +} +librc_hidden_def(rc_runlevel_stopping) + +char **rc_runlevel_list (void) +{ + return (ls_dir (RC_RUNLEVELDIR, LS_DIR)); +} +librc_hidden_def(rc_runlevel_list) + +char *rc_runlevel_get (void) +{ + FILE *fp; + char *runlevel = NULL; + + if ((fp = fopen (SOFTLEVEL, "r"))) { + runlevel = xmalloc (sizeof (char) * PATH_MAX); + if (fgets (runlevel, PATH_MAX, fp)) { + int i = strlen (runlevel) - 1; + if (runlevel[i] == '\n') + runlevel[i] = 0; + } else + *runlevel = '\0'; + fclose (fp); + } + + if (! runlevel || ! *runlevel) { + free (runlevel); + runlevel = xstrdup (RC_LEVEL_SYSINIT); + } + + return (runlevel); +} +librc_hidden_def(rc_runlevel_get) + +bool rc_runlevel_set (const char *runlevel) +{ + FILE *fp = fopen (SOFTLEVEL, "w"); + + if (! fp) + return (false); + fprintf (fp, "%s", runlevel); + fclose (fp); + return (true); +} +librc_hidden_def(rc_runlevel_set) + +bool rc_runlevel_exists (const char *runlevel) +{ + char *path; + struct stat buf; + bool retval = false; + + if (! runlevel) + return (false); + + path = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, (char *) NULL); + if (stat (path, &buf) == 0 && S_ISDIR (buf.st_mode)) + retval = true; + free (path); + return (retval); +} +librc_hidden_def(rc_runlevel_exists) + +/* Resolve a service name to it's full path */ +char *rc_service_resolve (const char *service) +{ + char buffer[PATH_MAX]; + char *file; + int r = 0; + struct stat buf; + + if (! service) + return (NULL); + + if (service[0] == '/') + return (xstrdup (service)); + + file = rc_strcatpaths (RC_SVCDIR, "started", service, (char *) NULL); + if (lstat (file, &buf) || ! S_ISLNK (buf.st_mode)) { + free (file); + file = rc_strcatpaths (RC_SVCDIR, "inactive", service, (char *) NULL); + if (lstat (file, &buf) || ! S_ISLNK (buf.st_mode)) { + free (file); + file = NULL; + } + } + + memset (buffer, 0, sizeof (buffer)); + if (file) { + r = readlink (file, buffer, sizeof (buffer)); + free (file); + if (r > 0) + return (xstrdup (buffer)); + } + snprintf (buffer, sizeof (buffer), RC_INITDIR "/%s", service); + + /* So we don't exist in /etc/init.d - check /usr/local/etc/init.d */ + if (stat (buffer, &buf) != 0) { + snprintf (buffer, sizeof (buffer), RC_INITDIR_LOCAL "/%s", service); + if (stat (buffer, &buf) != 0) + return (NULL); + } + + return (xstrdup (buffer)); +} +librc_hidden_def(rc_service_resolve) + +bool rc_service_exists (const char *service) +{ + char *file; + bool retval = false; + int len; + struct stat buf; + + if (! service) + return (false); + + len = strlen (service); + + /* .sh files are not init scripts */ + if (len > 2 && service[len - 3] == '.' && + service[len - 2] == 's' && + service[len - 1] == 'h') + return (false); + + file = rc_service_resolve (service); + if (stat (file, &buf) == 0 && buf.st_mode & S_IXUGO) + retval = true; + free (file); + return (retval); +} +librc_hidden_def(rc_service_exists) + +#define OPTSTR ". '%s'; echo \"${opts}\"" +char **rc_service_extra_commands (const char *service) +{ + char *svc; + char *cmd = NULL; + char *buffer = NULL; + char **commands = NULL; + char *token; + char *p = buffer; + FILE *fp; + int l; + + if (! (svc = rc_service_resolve (service))) + return (NULL); + + l = strlen (OPTSTR) + strlen (svc) + 1; + cmd = xmalloc (sizeof (char) * l); + snprintf (cmd, l, OPTSTR, svc); + free (svc); + if ((fp = popen (cmd, "r"))) { + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + if (fgets (buffer, RC_LINEBUFFER, fp)) { + if (buffer[strlen (buffer) - 1] == '\n') + buffer[strlen (buffer) - 1] = '\0'; + while ((token = strsep (&p, " "))) + rc_strlist_addsort (&commands, token); + } + pclose (fp); + free (buffer); + } + free (cmd); + return (commands); +} +librc_hidden_def(rc_service_extra_commands) + +#define DESCSTR ". '%s'; echo \"${description%s%s}\"" +char *rc_service_description (const char *service, const char *option) +{ + char *svc; + char *cmd = NULL; + char *buffer; + char *desc = NULL; + FILE *fp; + int i; + int l; + + if (! (svc = rc_service_resolve (service))) + return (NULL); + + if (! option) + option = ""; + + l = strlen (DESCSTR) + strlen (svc) + strlen (option) + 2; + cmd = xmalloc (sizeof (char) * l); + snprintf (cmd, l, DESCSTR, svc, option ? "_" : "", option); + free (svc); + if ((fp = popen (cmd, "r"))) { + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + while (fgets (buffer, RC_LINEBUFFER, fp)) { + if (! desc) { + desc = xmalloc (strlen (buffer) + 1); + *desc = '\0'; + } else { + desc = xrealloc (desc, strlen (desc) + strlen (buffer) + 1); + } + i = strlen (desc); + memcpy (desc + i, buffer, strlen (buffer)); + memset (desc + i + strlen (buffer), 0, 1); + } + free (buffer); + pclose (fp); + } + free (cmd); + return (desc); +} +librc_hidden_def(rc_service_description) + +bool rc_service_in_runlevel (const char *service, const char *runlevel) +{ + char *file; + bool retval; + + if (! runlevel || ! service) + return (false); + + file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename_c (service), + (char *) NULL); + retval = exists (file); + free (file); + + return (retval); +} +librc_hidden_def(rc_service_in_runlevel) + +bool rc_service_mark (const char *service, const rc_service_state_t state) +{ + char *file; + int i = 0; + int skip_state = -1; + const char *base; + char *init = rc_service_resolve (service); + bool skip_wasinactive = false; + + if (! init) + return (false); + + base = basename_c (service); + + if (state != RC_SERVICE_STOPPED) { + if (! exists (init)) { + free (init); + return (false); + } + + file = rc_strcatpaths (RC_SVCDIR, rc_parse_service_state (state), base, + (char *) NULL); + if (exists (file)) + unlink (file); + i = symlink (init, file); + if (i != 0) { + free (file); + free (init); + return (false); + } + + free (file); + skip_state = state; + } + + if (state == RC_SERVICE_COLDPLUGGED || state == RC_SERVICE_FAILED) { + free (init); + return (true); + } + + /* Remove any old states now */ + for (i = 0; rc_service_state_names[i].name; i++) { + int s = rc_service_state_names[i].state; + + if ((s != skip_state && + s != RC_SERVICE_STOPPED && + s != RC_SERVICE_COLDPLUGGED && + s != RC_SERVICE_SCHEDULED) && + (! skip_wasinactive || s != RC_SERVICE_WASINACTIVE)) + { + file = rc_strcatpaths (RC_SVCDIR, rc_parse_service_state (s), base, + (char *) NULL); + if (exists (file)) { + if ((state == RC_SERVICE_STARTING || + state == RC_SERVICE_STOPPING) && + s == RC_SERVICE_INACTIVE) + { + char *wasfile = rc_strcatpaths (RC_SVCDIR, + rc_parse_service_state (RC_SERVICE_WASINACTIVE), + base, (char *) NULL); + + symlink (init, wasfile); + skip_wasinactive = true; + free (wasfile); + } + unlink (file); + } + free (file); + } + } + + /* Remove the exclusive state if we're inactive */ + if (state == RC_SERVICE_STARTED || + state == RC_SERVICE_STOPPED || + state == RC_SERVICE_INACTIVE) + { + file = rc_strcatpaths (RC_SVCDIR, "exclusive", base, (char *) NULL); + unlink (file); + free (file); + } + + /* Remove any options and daemons the service may have stored */ + if (state == RC_SERVICE_STOPPED) { + char *dir = rc_strcatpaths (RC_SVCDIR, "options", base, (char *) NULL); + rm_dir (dir, true); + free (dir); + + dir = rc_strcatpaths (RC_SVCDIR, "daemons", base, (char *) NULL); + rm_dir (dir, true); + free (dir); + + rc_service_schedule_clear (service); + } + + /* These are final states, so remove us from scheduled */ + if (state == RC_SERVICE_STARTED || state == RC_SERVICE_STOPPED) { + char *sdir = rc_strcatpaths (RC_SVCDIR, "scheduled", (char *) NULL); + char **dirs = ls_dir (sdir, 0); + char *dir; + int serrno; + + STRLIST_FOREACH (dirs, dir, i) { + char *bdir = rc_strcatpaths (sdir, dir, (char *) NULL); + file = rc_strcatpaths (bdir, base, (char *) NULL); + unlink (file); + free (file); + + /* Try and remove the dir - we don't care about errors */ + serrno = errno; + rmdir (bdir); + errno = serrno; + free (bdir); + } + rc_strlist_free (dirs); + free (sdir); + } + + free (init); + return (true); +} +librc_hidden_def(rc_service_mark) + +rc_service_state_t rc_service_state (const char *service) +{ + int i; + int state = RC_SERVICE_STOPPED; + + for (i = 0; rc_service_state_names[i].name; i++) { + char *file = rc_strcatpaths (RC_SVCDIR, rc_service_state_names[i].name, + basename_c (service), (char*) NULL); + if (exists (file)) { + if (rc_service_state_names[i].state <= 0x10) + state = rc_service_state_names[i].state; + else + state |= rc_service_state_names[i].state; + } + free (file); + } + + if (state & RC_SERVICE_STOPPED) { + char **services = rc_services_scheduled_by (service); + if (services) { + state |= RC_SERVICE_SCHEDULED; + free (services); + } + } + + return (state); +} +librc_hidden_def(rc_service_state) + +char *rc_service_value_get (const char *service, const char *option) +{ + FILE *fp; + char *buffer = NULL; + char *file = rc_strcatpaths (RC_SVCDIR, "options", service, option, + (char *) NULL); + + if ((fp = fopen (file, "r"))) { + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + fgets (buffer, RC_LINEBUFFER, fp); + fclose (fp); + } + free (file); + + return (buffer); +} +librc_hidden_def(rc_service_value_get) + +bool rc_service_value_set (const char *service, const char *option, + const char *value) +{ + FILE *fp; + char *path = rc_strcatpaths (RC_SVCDIR, "options", service, (char *) NULL); + char *file = rc_strcatpaths (path, option, (char *) NULL); + bool retval = false; + + if (mkdir (path, 0755) != 0 && errno != EEXIST) { + free (path); + free (file); + return (false); + } + + if ((fp = fopen (file, "w"))) { + if (value) + fprintf (fp, "%s", value); + fclose (fp); + retval = true; + } + + free (path); + free (file); + return (retval); +} +librc_hidden_def(rc_service_value_set) + +static pid_t _exec_service (const char *service, const char *arg) +{ + char *file; + char *fifo; + pid_t pid = -1; + + file = rc_service_resolve (service); + if (! exists (file)) { + rc_service_mark (service, RC_SERVICE_STOPPED); + free (file); + return (0); + } + + /* We create a fifo so that other services can wait until we complete */ + fifo = rc_strcatpaths (RC_SVCDIR, "exclusive", basename_c (service), + (char *) NULL); + + if (mkfifo (fifo, 0600) != 0 && errno != EEXIST) { + free (fifo); + free (file); + return (-1); + } + + if ((pid = vfork ()) == 0) { + execl (file, file, arg, (char *) NULL); + fprintf (stderr, "unable to exec `%s': %s\n", file, strerror (errno)); + unlink (fifo); + _exit (EXIT_FAILURE); + } + + free (fifo); + free (file); + + if (pid == -1) + fprintf (stderr, "vfork: %s\n", strerror (errno)); + + return (pid); +} + +pid_t rc_service_stop (const char *service) +{ + rc_service_state_t state = rc_service_state (service); + + if (state & RC_SERVICE_FAILED) + return (-1); + + if (state & RC_SERVICE_STOPPED) + return (0); + + return (_exec_service (service, "stop")); +} +librc_hidden_def(rc_service_stop) + +pid_t rc_service_start (const char *service) +{ + rc_service_state_t state = rc_service_state (service); + + if (state & RC_SERVICE_FAILED) + return (-1); + + if (! state & RC_SERVICE_STOPPED) + return (0); + + return (_exec_service (service, "start")); +} +librc_hidden_def(rc_service_start) + +bool rc_service_schedule_start (const char *service, + const char *service_to_start) +{ + char *dir; + char *init; + char *file; + bool retval; + + /* service may be a provided service, like net */ + if (! service || ! rc_service_exists (service_to_start)) + return (false); + + dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename_c (service), + (char *) NULL); + if (mkdir (dir, 0755) != 0 && errno != EEXIST) { + free (dir); + return (false); + } + + init = rc_service_resolve (service_to_start); + file = rc_strcatpaths (dir, basename_c (service_to_start), (char *) NULL); + retval = (exists (file) || symlink (init, file) == 0); + free (init); + free (file); + free (dir); + + return (retval); +} +librc_hidden_def(rc_service_schedule_start) + +bool rc_service_schedule_clear (const char *service) +{ + char *dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename_c (service), + (char *) NULL); + bool retval; + + if (! (retval = rm_dir (dir, true)) && errno == ENOENT) + retval = true; + free (dir); + return (retval); +} +librc_hidden_def(rc_service_schedule_clear) + + +char **rc_services_in_runlevel (const char *runlevel) +{ + char *dir; + char **list = NULL; + + if (! runlevel) { + int i; + char **local = ls_dir (RC_INITDIR_LOCAL, LS_INITD); + + list = ls_dir (RC_INITDIR, LS_INITD); + STRLIST_FOREACH (local, dir, i) + rc_strlist_addsortu (&list, dir); + rc_strlist_free (local); + return (list); + } + + /* These special levels never contain any services */ + if (strcmp (runlevel, RC_LEVEL_SYSINIT) == 0 || + strcmp (runlevel, RC_LEVEL_SINGLE) == 0) + return (NULL); + + dir = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, (char *) NULL); + list = ls_dir (dir, LS_INITD); + free (dir); + return (list); +} +librc_hidden_def(rc_services_in_runlevel) + +char **rc_services_in_state (rc_service_state_t state) +{ + char *dir = rc_strcatpaths (RC_SVCDIR, rc_parse_service_state (state), + (char *) NULL); + char **list = NULL; + + if (state == RC_SERVICE_SCHEDULED) { + char **dirs = ls_dir (dir, 0); + char *d; + int i; + + STRLIST_FOREACH (dirs, d, i) { + char *p = rc_strcatpaths (dir, d, (char *) NULL); + char **entries = ls_dir (p, LS_INITD); + char *e; + int j; + + STRLIST_FOREACH (entries, e, j) + rc_strlist_addsortu (&list, e); + + if (entries) + free (entries); + } + + if (dirs) + free (dirs); + } else { + list = ls_dir (dir, LS_INITD); + } + + free (dir); + return (list); +} +librc_hidden_def(rc_services_in_state) + +bool rc_service_add (const char *runlevel, const char *service) +{ + bool retval; + char *init; + char *file; + + if (! rc_runlevel_exists (runlevel)) { + errno = ENOENT; + return (false); + } + + if (rc_service_in_runlevel (service, runlevel)) { + errno = EEXIST; + return (false); + } + + init = rc_service_resolve (service); + + /* We need to ensure that only things in /etc/init.d are added + * to the boot runlevel */ + if (strcmp (runlevel, RC_LEVEL_BOOT) == 0) { + char *tmp = xstrdup (init); + retval = (strcmp (dirname (tmp), RC_INITDIR) == 0); + free (tmp); + if (! retval) { + free (init); + errno = EPERM; + return (false); + } + } + + file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename_c (service), + (char *) NULL); + retval = (symlink (init, file) == 0); + free (init); + free (file); + return (retval); +} +librc_hidden_def(rc_service_add) + +bool rc_service_delete (const char *runlevel, const char *service) +{ + char *file; + bool retval = false; + + if (! runlevel || ! service) + return (false); + + file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename_c (service), + (char *) NULL); + if (unlink (file) == 0) + retval = true; + + free (file); + return (retval); +} +librc_hidden_def(rc_service_delete) + +char **rc_services_scheduled_by (const char *service) +{ + char **dirs = ls_dir (RC_SVCDIR "/scheduled", 0); + char **list = NULL; + char *dir; + int i; + + STRLIST_FOREACH (dirs, dir, i) { + char *file = rc_strcatpaths (RC_SVCDIR, "scheduled", dir, service, + (char *) NULL); + if (exists (file)) + rc_strlist_add (&list, file); + free (file); + } + rc_strlist_free (dirs); + + return (list); +} +librc_hidden_def(rc_services_scheduled_by) + +char **rc_services_scheduled (const char *service) +{ + char *dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename_c (service), + (char *) NULL); + char **list = NULL; + + list = ls_dir (dir, LS_INITD); + free (dir); + return (list); +} +librc_hidden_def(rc_services_scheduled) + +bool rc_service_plugable (const char *service) +{ + char *list; + char *p; + char *star; + char *token; + bool allow = true; + char *match = getenv ("RC_PLUG_SERVICES"); + if (! match) + return true; + + list = xstrdup (match); + p = list; + while ((token = strsep (&p, " "))) { + bool truefalse = true; + if (token[0] == '!') { + truefalse = false; + token++; + } + + star = strchr (token, '*'); + if (star) { + if (strncmp (service, token, star - token) == 0) { + allow = truefalse; + break; + } + } else { + if (strcmp (service, token) == 0) { + allow = truefalse; + break; + } + } + } + + free (list); + return (allow); +} +librc_hidden_def(rc_service_plugable) diff --git a/src/librc/librc.h b/src/librc/librc.h new file mode 100644 index 00000000..cf61217a --- /dev/null +++ b/src/librc/librc.h @@ -0,0 +1,127 @@ +/* + * librc.h + * Internal header file to setup build env for files in librc.so + */ + +/* + * 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_H_ +#define _LIBRC_H_ + +#define _IN_LIBRC + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__DragonFly__) || defined(__FreeBSD__) || \ + defined(__NetBSD__) || defined (__OpenBSD__) +#include +#include +#include +#include +#endif + +#include "librc-depend.h" +#include "rc.h" +#include "rc-misc.h" +#include "strlist.h" + +#include "hidden-visibility.h" +#define librc_hidden_proto(x) hidden_proto(x) +#define librc_hidden_def(x) hidden_def(x) + +librc_hidden_proto(rc_config_list) +librc_hidden_proto(rc_config_load) +librc_hidden_proto(rc_config_value) +librc_hidden_proto(rc_deptree_depend) +librc_hidden_proto(rc_deptree_depends) +librc_hidden_proto(rc_deptree_free) +librc_hidden_proto(rc_deptree_load) +librc_hidden_proto(rc_deptree_order) +librc_hidden_proto(rc_deptree_update) +librc_hidden_proto(rc_deptree_update_needed) +librc_hidden_proto(rc_find_pids) +librc_hidden_proto(rc_runlevel_exists) +librc_hidden_proto(rc_runlevel_get) +librc_hidden_proto(rc_runlevel_list) +librc_hidden_proto(rc_runlevel_set) +librc_hidden_proto(rc_runlevel_starting) +librc_hidden_proto(rc_runlevel_stopping) +librc_hidden_proto(rc_service_add) +librc_hidden_proto(rc_service_daemons_crashed) +librc_hidden_proto(rc_service_daemon_set) +librc_hidden_proto(rc_service_delete) +librc_hidden_proto(rc_service_description) +librc_hidden_proto(rc_service_exists) +librc_hidden_proto(rc_service_extra_commands) +librc_hidden_proto(rc_service_in_runlevel) +librc_hidden_proto(rc_service_mark) +librc_hidden_proto(rc_service_plugable) +librc_hidden_proto(rc_service_resolve) +librc_hidden_proto(rc_service_schedule_clear) +librc_hidden_proto(rc_service_schedule_start) +librc_hidden_proto(rc_service_start) +librc_hidden_proto(rc_service_stop) +librc_hidden_proto(rc_services_in_runlevel) +librc_hidden_proto(rc_services_in_state) +librc_hidden_proto(rc_services_scheduled) +librc_hidden_proto(rc_services_scheduled_by) +librc_hidden_proto(rc_service_started_daemon) +librc_hidden_proto(rc_service_state) +librc_hidden_proto(rc_service_value_get) +librc_hidden_proto(rc_service_value_set) +librc_hidden_proto(rc_strcatpaths) +librc_hidden_proto(rc_strlist_add) +librc_hidden_proto(rc_strlist_addu) +librc_hidden_proto(rc_strlist_addsort) +librc_hidden_proto(rc_strlist_addsortc) +librc_hidden_proto(rc_strlist_addsortu) +librc_hidden_proto(rc_strlist_delete) +librc_hidden_proto(rc_strlist_free) +librc_hidden_proto(rc_strlist_join) +librc_hidden_proto(rc_strlist_reverse) +librc_hidden_proto(rc_yesno) + +#endif diff --git a/src/librc/rc.h b/src/librc/rc.h new file mode 100644 index 00000000..0020764e --- /dev/null +++ b/src/librc/rc.h @@ -0,0 +1,446 @@ +/* + * 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 __RC_H__ +#define __RC_H__ + +#ifdef __GNUC__ +# define GCC_VERSION (__GNUC__ * 1000 + __GNUC__MINOR) +# if (GCC_VERSION >= 3005) +# define SENTINEL __attribute__ ((__sentinel__)) +# endif +#endif +#ifndef SENTINEL +# define SENTINEL +#endif + +#include +#include + +/*! @name Reserved runlevel names */ +#define RC_LEVEL_SYSINIT "sysinit" +#define RC_LEVEL_SINGLE "single" +#define RC_LEVEL_SHUTDOWN "shutdown" +#define RC_LEVEL_REBOOT "reboot" + +/*! Return the current runlevel. + * @return the current runlevel */ +char *rc_runlevel_get (void); + +/*! Checks if the runlevel exists or not + * @param runlevel to check + * @return true if the runlevel exists, otherwise false */ +bool rc_runlevel_exists (const char *runlevel); + +/*! Return a NULL terminated list of runlevels + * @return a NULL terminated list of runlevels */ +char **rc_runlevel_list (void); + +/*! Set the runlevel. + * This just changes the stored runlevel and does not start or stop any services. + * @param runlevel to store */ +bool rc_runlevel_set (const char *runlevel); + +/*! Is the runlevel starting? + * @return true if yes, otherwise false */ +bool rc_runlevel_starting (void); + +/*! Is the runlevel stopping? + * @return true if yes, otherwise false */ +bool rc_runlevel_stopping (void); + +/*! @name RC + * A service can be given as a full path or just its name. + * If its just a name then we try to resolve the service to a full path. + * This should allow the use if local init.d directories in the future. */ + +/*! @brief States a service can be in */ +typedef enum +{ + /* These are actual states + * The service has to be in one only at all times */ + RC_SERVICE_STOPPED = 0x0001, + RC_SERVICE_STARTED = 0x0002, + RC_SERVICE_STOPPING = 0x0004, + RC_SERVICE_STARTING = 0x0008, + RC_SERVICE_INACTIVE = 0x0010, + + /* Service may or may not have been coldplugged */ + RC_SERVICE_COLDPLUGGED = 0x0100, + + /* Optional states service could also be in */ + RC_SERVICE_FAILED = 0x0200, + RC_SERVICE_SCHEDULED = 0x0400, + RC_SERVICE_WASINACTIVE = 0x0800 +} rc_service_state_t; + +/*! Add the service to the runlevel + * @param runlevel to add to + * @param service to add + * @return true if successful, otherwise false */ +bool rc_service_add (const char *runlevel, const char *service); + +/*! Remove the service from the runlevel + * @param runlevel to remove from + * @param service to remove + * @return true if sucessful, otherwise false */ +bool rc_service_delete (const char *runlevel, const char *service); + +/*! Save the arguments to find a running daemon + * @param service to save arguments for + * @param exec that we started + * @param name of the process (optional) + * @param pidfile of the process (optional) + * @param started if true, add the arguments otherwise remove existing matching arguments */ +bool rc_service_daemon_set (const char *service, const char *exec, + const char *name, const char *pidfile, + bool started); + +/*! Returns a description of what the service and/or option does. + * @param service to check + * @param option to check (if NULL, service description) + * @return a newly allocated pointer to the description */ +char *rc_service_description (const char *service, const char *option); + +/*! Checks if a service exists or not. + * @param service to check + * @return true if service exists, otherwise false */ +bool rc_service_exists (const char *service); + +/*! Checks if a service is in a runlevel + * @param service to check + * @param runlevel it should be in + * @return true if service is in the runlevel, otherwise false */ +bool rc_service_in_runlevel (const char *service, const char *runlevel); + +/*! Marks the service state + * @param service to mark + * @param state service should be in + * @return true if service state change was successful, otherwise false */ +bool rc_service_mark (const char *service, rc_service_state_t state); + +/*! Lists the extra commands a service has + * @param service to load the commands from + * @return NULL terminated string list of commands */ +char **rc_service_extra_commands (const char *service); + +/*! Check if the service is allowed to be hot/cold plugged + * @param service to check + * @return true if allowed, otherwise false */ +bool rc_service_plugable (const char *service); + +/*! Resolves a service name to its full path. + * @param service to check + * @return pointer to full path of service */ +char *rc_service_resolve (const char *service); + +/*! Schedule a service to be started when another service starts + * @param service that starts the scheduled service when started + * @param service_to_start service that will be started */ +bool rc_service_schedule_start (const char *service, + const char *service_to_start); +/*! Return a NULL terminated list of services that are scheduled to start + * when the given service has started + * @param service to check + * @return NULL terminated list of services scheduled to start */ +char **rc_services_scheduled_by (const char *service); + +/*! Clear the list of services scheduled to be started by this service + * @param service to clear + * @return true if no errors, otherwise false */ +bool rc_service_schedule_clear (const char *service); + +/*! Checks if a service in in a state + * @param service to check + * @return state of the service */ +rc_service_state_t rc_service_state (const char *service); + +/*! Start a service + * @param service to start + * @return pid of the service starting process */ +pid_t rc_service_start (const char *service); + +/*! Stop a service + * @param service to stop + * @return pid of service stopping process */ +pid_t rc_service_stop (const char *service); + +/*! Check if the service started the daemon + * @param service to check + * @param exec to check + * @param indx of the daemon (optional - 1st daemon, 2nd daemon, etc) + * @return true if started by this service, otherwise false */ +bool rc_service_started_daemon (const char *service, const char *exec, + int indx); + +/*! Return a saved value for a service + * @param service to check + * @param option to load + * @return saved value */ +char *rc_service_value_get (const char *service, const char *option); + +/*! Save a persistent value for a service + * @param service to save for + * @param option to save + * @param value of the option + * @return true if saved, otherwise false */ +bool rc_service_value_set (const char *service, const char *option, + const char *value); + +/*! List the services in a runlevel + * @param runlevel to list + * @return NULL terminated list of services */ +char **rc_services_in_runlevel (const char *runlevel); + +/*! List the services in a state + * @param state to list + * @return NULL terminated list of services */ +char **rc_services_in_state (rc_service_state_t state); + +/*! List the services shceduled to start when this one does + * @param service to check + * @return NULL terminated list of services */ +char **rc_services_scheduled (const char *service); + +/*! Checks that all daemons started with start-stop-daemon by the service + * are still running. + * @param service to check + * @return true if all daemons started are still running, otherwise false */ +bool rc_service_daemons_crashed (const char *service); + +/*! @name Dependency options + * These options can change the services found by the rc_get_depinfo and + * rc_get_depends functions. */ +/*! Trace provided services */ +#define RC_DEP_TRACE 0x01 +/*! Only use services added to runlevels */ +#define RC_DEP_STRICT 0x02 +/*! Runlevel is starting */ +#define RC_DEP_START 0x04 +/*! Runlevel is stopping */ +#define RC_DEP_STOP 0x08 + +/*! @name Dependencies + * We analyse each init script and cache the resultant dependency tree. + * This tree can be accessed using the below functions. */ + +#ifndef _IN_LIBRC +/* Handles to internal structures */ +typedef void *rc_depinfo_t; +#endif + +/*! Update the cached dependency tree if it's older than any init script, + * its configuration file or an external configuration file the init script + * has specified. + * @return true if successful, otherwise false */ +bool rc_deptree_update (void); + +/*! Check if the cached dependency tree is older than any init script, + * its configuration file or an external configuration file the init script + * has specified. + * @return true if it needs updating, otherwise false */ +bool rc_deptree_update_needed (void); + +/*! Load the cached dependency tree and return a pointer to it. + * This pointer should be freed with rc_deptree_free when done. + * @return pointer to the dependency tree */ +rc_depinfo_t *rc_deptree_load (void); + +/*! List the depend for the type of service + * @param deptree to search + * @param type to use (keywords, etc) + * @param service to check + * @return NULL terminated list of services in order */ +char **rc_deptree_depend (const rc_depinfo_t *deptree, + const char *type, const char *service); + +/*! List all the services in order that the given services have + * for the given types and options. + * @param deptree to search + * @param types to use (ineed, iuse, etc) + * @param services to check + * @param options to pass + * @return NULL terminated list of services in order */ +char **rc_deptree_depends (const rc_depinfo_t *deptree, + const char *const *types, + const char *const *services, const char *runlevel, + int options); + +/*! List all the services that should be stoppned and then started, in order, + * for the given runlevel, including sysinit and boot services where + * approriate. + * @param deptree to search + * @param runlevel to change into + * @param options to pass + * @return NULL terminated list of services in order */ +char **rc_deptree_order (const rc_depinfo_t *deptree, const char *runlevel, + int options); + +/*! Free a deptree and its information + * @param deptree to free */ +void rc_deptree_free (rc_depinfo_t *deptree); + +/*! @name Plugins + * For each plugin loaded we will call rc_plugin_hook with the below + * enum and either the runlevel name or service name. + * + * Plugins are called when rc does something. This does not indicate an + * end result and the plugin should use the above functions to query things + * like service status. + * + * The service hooks have extra ones - now and done. This is because after + * start_in we may start other services before we start the service in + * question. now shows we really will start the service now and done shows + * when we have done it as may start scheduled services at this point. */ +/*! Points at which a plugin can hook into RC */ +typedef enum +{ + RC_HOOK_RUNLEVEL_STOP_IN = 1, + RC_HOOK_RUNLEVEL_STOP_OUT = 4, + RC_HOOK_RUNLEVEL_START_IN = 5, + RC_HOOK_RUNLEVEL_START_OUT = 8, + /*! We send the abort if an init script requests we abort and drop + * into single user mode if system not fully booted */ + RC_HOOK_ABORT = 99, + RC_HOOK_SERVICE_STOP_IN = 101, + RC_HOOK_SERVICE_STOP_NOW = 102, + RC_HOOK_SERVICE_STOP_DONE = 103, + RC_HOOK_SERVICE_STOP_OUT = 104, + RC_HOOK_SERVICE_START_IN = 105, + RC_HOOK_SERVICE_START_NOW = 106, + RC_HOOK_SERVICE_START_DONE = 107, + RC_HOOK_SERVICE_START_OUT = 108 +} rc_hook_t; + +/*! Plugin entry point + * @param hook point + * @param name of runlevel or service + * @return 0 for success otherwise -1 */ +int rc_plugin_hook (rc_hook_t hook, const char *name); + +/*! Plugins should write FOO=BAR to this fd to set any environment + * variables they wish. Variables should be separated by NULLs. */ +extern FILE *rc_environ_fd; + +/*! @name Configuration + * These functions help to deal with shell based configuration files */ +/*! Return a NULL terminated list of non comment lines from a file. */ +char **rc_config_list (const char *file); + +/*! Return a NULL terminated list of key=value lines from a file. */ +char **rc_config_load (const char *file); + +/*! Return the value of the entry from a key=value list. */ +char *rc_config_value (char **list, const char *entry); + +/*! Check if a variable is a boolean and return it's value. + * If variable is not a boolean then we set errno to be ENOENT when it does + * not exist or EINVAL if it's not a boolean. + * @param variable to check + * @return true if it matches true, yes or 1, false if otherwise. */ +bool rc_yesno (const char *variable); + +/*! @name String List functions + * Handy functions for dealing with string arrays of char **. + * It's safe to assume that any function here that uses char ** is a string + * list that can be manipulated with the below functions. Every string list + * should be released with a call to rc_strlist_free. */ + +/*! Duplicate the item, add it to end of the list and return a pointer to it. + * @param list to add the item too + * @param item to add. + * @return pointer to newly added item */ +char *rc_strlist_add (char ***list, const char *item); + +/*! If the item does not exist in the list, duplicate it, add it to the + * list and then return a pointer to it. + * @param list to add the item too + * @param item to add. + * @return pointer to newly added item */ +char *rc_strlist_addu (char ***list, const char *item); + +/*! Duplicate the item, add it to the list at the point based on locale and + * then return a pointer to it. + * @param list to add the item too + * @param item to add. + * @return pointer to newly added item */ +char *rc_strlist_addsort (char ***list, const char *item); + +/*! Duplicate the item, add it to the list at the point based on C locale and + * then return a pointer to it. + * @param list to add the item too + * @param item to add. + * @return pointer to newly added item */ +char *rc_strlist_addsortc (char ***list, const char *item); + +/*! If the item does not exist in the list, duplicate it, add it to the + * list based on locale and then return a pointer to it. + * @param list to add the item too + * @param item to add. + * @return pointer to newly added item */ +char *rc_strlist_addsortu (char ***list, const char *item); + +/*! Free the item and remove it from the list. Return 0 on success otherwise -1. + * @param list to add the item too + * @param item to add. + * @return true on success, otherwise false */ +bool rc_strlist_delete (char ***list, const char *item); + +/*! Moves the contents of list2 onto list1, so list2 is effectively emptied. + * Returns a pointer to the last item on the new list. + * @param list1 to append to + * @param list2 to move from + * @return pointer to the last item on the list */ +char *rc_strlist_join (char ***list1, char **list2); + +/*! Reverses the contents of the list. + * @param list to reverse */ +void rc_strlist_reverse (char **list); + +/*! Frees each item on the list and the list itself. + * @param list to free */ +void rc_strlist_free (char **list); + +/*! Concatenate paths adding '/' if needed. The resultant pointer should be + * freed when finished with. + * @param path1 starting path + * @param paths NULL terminated list of paths to add + * @return pointer to the new path */ +char *rc_strcatpaths (const char *path1, const char *paths, ...) SENTINEL; + +/*! Find processes based on criteria. + * All of these are optional. + * pid overrides anything else. + * If both exec and cmd are given then we ignore exec. + * @param exec to check for + * @param cmd to check for + * @param uid to check for + * @param pid to check for + * @return NULL terminated list of pids */ +pid_t *rc_find_pids (const char *exec, const char *cmd, + uid_t uid, pid_t pid); + +#endif diff --git a/src/mountinfo.c b/src/mountinfo.c deleted file mode 100644 index 05ce8dd7..00000000 --- a/src/mountinfo.c +++ /dev/null @@ -1,464 +0,0 @@ -/* - 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 - -#if defined(__DragonFly__) || defined(__FreeBSD__) || \ - defined(__NetBSD__) || defined(__OpenBSD__) -#define BSD -#include -#include -#include -#elif defined (__linux__) -#include -#endif - -#include -#include -#include -#include -#include -#include -#include - -#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/ncurses.mk b/src/ncurses.mk new file mode 100644 index 00000000..f1df47f0 --- /dev/null +++ b/src/ncurses.mk @@ -0,0 +1,3 @@ +LIBTERMCAP?= -lncurses +CPPFLAGS+= -DHAVE_TERMCAP +LDADD+= ${LIBTERMCAP} diff --git a/src/os.mk b/src/os.mk new file mode 100644 index 00000000..6b8b56d9 --- /dev/null +++ b/src/os.mk @@ -0,0 +1,24 @@ +# Copyright 2008 Roy Marples + +# Generic definitions + +_OS_SH= u=`uname -s`; case "$${u}" in *BSD|DragonFly) echo "BSD";; *) echo "$${u}";; esac +_OS!= $(_OS_SH) +OS?= $(_OS)$(shell $(_OS_SH)) + +_LIBNAME_SH= l=`readlink /lib`; case "$$l" in /lib64|lib64) echo "lib64";; *) echo "lib";; esac +_LIBNAME!= $(_LIBNAME_SH) +LIBNAME?= $(_LIBNAME)$(shell $(_LIBNAME_SH)) +RC_LIB= /$(LIB)/rc + +_DEF_SH= case `uname -s` in Linux) echo "-D_XOPEN_SOURCE=600 -D_BSD_SOURCE";; *) echo;; esac +_DEF!= $(_DEF_SH) +CPPFLAGS+= $(_DEF)$(shell $(_DEF_SH)) + +_LIBDL_SH= case `uname -s` in Linux) echo "-Wl,-Bdynamic -ldl";; *) echo;; esac +_LIBDL!= $(_LIBDL_SH) +LIBDL?= $(_LIBDL)$(shell $(_LIBDL_SH)) + +_LIBKVM_SH= case `uname -s` in *BSD) echo "-lkvm";; *) echo;; esac +_LIBKVM!= $(_LIBKVM_SH) +LIBKVM?= $(_LIBKVM)$(shell $(_LIBKVM_SH)) diff --git a/src/pam.mk b/src/pam.mk new file mode 100644 index 00000000..a9c961ed --- /dev/null +++ b/src/pam.mk @@ -0,0 +1,6 @@ +LIBPAM?= -lpam +CPPFLAGS+= -DHAVE_PAM +LDADD+= ${LIBPAM} + +PAMDIR?= /etc/pam.d +PAMMODE?= 0644 diff --git a/src/prog.mk b/src/prog.mk new file mode 100644 index 00000000..1d0f013f --- /dev/null +++ b/src/prog.mk @@ -0,0 +1,18 @@ +# rules to build a library +# based on FreeBSD's bsd.prog.mk + +# Copyright 2008 Roy Marples + +BINDIR?= /sbin + +OBJS+= ${SRCS:.c=.o} + +INSTALL?= install + +all: ${PROG} + +${PROG}: ${SCRIPTS} ${OBJS} + ${CC} ${CFLAGS} ${LDFLAGS} ${PROGLDFLAGS} ${CPPFLAGS} -o $@ ${OBJS} ${LDADD} + +clean: + rm -f ${OBJS} ${PROG} ${CLEANFILES} diff --git a/src/rc-depend.c b/src/rc-depend.c deleted file mode 100644 index c5e9e662..00000000 --- a/src/rc-depend.c +++ /dev/null @@ -1,192 +0,0 @@ -/* - 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 -#include - -#include -#include -#include -#include -#include -#include - -#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-logger.c b/src/rc-logger.c deleted file mode 100644 index 675a4d23..00000000 --- a/src/rc-logger.c +++ /dev/null @@ -1,258 +0,0 @@ -/* - 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __linux__ -# include -#else -# include -#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-logger.h b/src/rc-logger.h deleted file mode 100644 index c15e73f8..00000000 --- a/src/rc-logger.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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-misc.c b/src/rc-misc.c deleted file mode 100644 index 0d8b8c1f..00000000 --- a/src/rc-misc.c +++ /dev/null @@ -1,353 +0,0 @@ -/* - 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 - -#ifdef __linux__ -#include -#include -#endif - -#include -#include -#include -#include -#include -#include - -#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-plugin.c b/src/rc-plugin.c deleted file mode 100644 index 613f049e..00000000 --- a/src/rc-plugin.c +++ /dev/null @@ -1,241 +0,0 @@ -/* - 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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-plugin.h b/src/rc-plugin.h deleted file mode 100644 index 412a47e7..00000000 --- a/src/rc-plugin.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - 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-status.c b/src/rc-status.c deleted file mode 100644 index 155192fa..00000000 --- a/src/rc-status.c +++ /dev/null @@ -1,197 +0,0 @@ -/* - 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 -#include -#include -#include -#include - -#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-update.c b/src/rc-update.c deleted file mode 100644 index 4f07503f..00000000 --- a/src/rc-update.c +++ /dev/null @@ -1,272 +0,0 @@ -/* - 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 -#include -#include -#include -#include -#include -#include -#include - -#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.c b/src/rc.c deleted file mode 100644 index a33b6dcf..00000000 --- a/src/rc.c +++ /dev/null @@ -1,1574 +0,0 @@ -/* - 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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.h b/src/rc.h deleted file mode 100644 index 0020764e..00000000 --- a/src/rc.h +++ /dev/null @@ -1,446 +0,0 @@ -/* - * 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 __RC_H__ -#define __RC_H__ - -#ifdef __GNUC__ -# define GCC_VERSION (__GNUC__ * 1000 + __GNUC__MINOR) -# if (GCC_VERSION >= 3005) -# define SENTINEL __attribute__ ((__sentinel__)) -# endif -#endif -#ifndef SENTINEL -# define SENTINEL -#endif - -#include -#include - -/*! @name Reserved runlevel names */ -#define RC_LEVEL_SYSINIT "sysinit" -#define RC_LEVEL_SINGLE "single" -#define RC_LEVEL_SHUTDOWN "shutdown" -#define RC_LEVEL_REBOOT "reboot" - -/*! Return the current runlevel. - * @return the current runlevel */ -char *rc_runlevel_get (void); - -/*! Checks if the runlevel exists or not - * @param runlevel to check - * @return true if the runlevel exists, otherwise false */ -bool rc_runlevel_exists (const char *runlevel); - -/*! Return a NULL terminated list of runlevels - * @return a NULL terminated list of runlevels */ -char **rc_runlevel_list (void); - -/*! Set the runlevel. - * This just changes the stored runlevel and does not start or stop any services. - * @param runlevel to store */ -bool rc_runlevel_set (const char *runlevel); - -/*! Is the runlevel starting? - * @return true if yes, otherwise false */ -bool rc_runlevel_starting (void); - -/*! Is the runlevel stopping? - * @return true if yes, otherwise false */ -bool rc_runlevel_stopping (void); - -/*! @name RC - * A service can be given as a full path or just its name. - * If its just a name then we try to resolve the service to a full path. - * This should allow the use if local init.d directories in the future. */ - -/*! @brief States a service can be in */ -typedef enum -{ - /* These are actual states - * The service has to be in one only at all times */ - RC_SERVICE_STOPPED = 0x0001, - RC_SERVICE_STARTED = 0x0002, - RC_SERVICE_STOPPING = 0x0004, - RC_SERVICE_STARTING = 0x0008, - RC_SERVICE_INACTIVE = 0x0010, - - /* Service may or may not have been coldplugged */ - RC_SERVICE_COLDPLUGGED = 0x0100, - - /* Optional states service could also be in */ - RC_SERVICE_FAILED = 0x0200, - RC_SERVICE_SCHEDULED = 0x0400, - RC_SERVICE_WASINACTIVE = 0x0800 -} rc_service_state_t; - -/*! Add the service to the runlevel - * @param runlevel to add to - * @param service to add - * @return true if successful, otherwise false */ -bool rc_service_add (const char *runlevel, const char *service); - -/*! Remove the service from the runlevel - * @param runlevel to remove from - * @param service to remove - * @return true if sucessful, otherwise false */ -bool rc_service_delete (const char *runlevel, const char *service); - -/*! Save the arguments to find a running daemon - * @param service to save arguments for - * @param exec that we started - * @param name of the process (optional) - * @param pidfile of the process (optional) - * @param started if true, add the arguments otherwise remove existing matching arguments */ -bool rc_service_daemon_set (const char *service, const char *exec, - const char *name, const char *pidfile, - bool started); - -/*! Returns a description of what the service and/or option does. - * @param service to check - * @param option to check (if NULL, service description) - * @return a newly allocated pointer to the description */ -char *rc_service_description (const char *service, const char *option); - -/*! Checks if a service exists or not. - * @param service to check - * @return true if service exists, otherwise false */ -bool rc_service_exists (const char *service); - -/*! Checks if a service is in a runlevel - * @param service to check - * @param runlevel it should be in - * @return true if service is in the runlevel, otherwise false */ -bool rc_service_in_runlevel (const char *service, const char *runlevel); - -/*! Marks the service state - * @param service to mark - * @param state service should be in - * @return true if service state change was successful, otherwise false */ -bool rc_service_mark (const char *service, rc_service_state_t state); - -/*! Lists the extra commands a service has - * @param service to load the commands from - * @return NULL terminated string list of commands */ -char **rc_service_extra_commands (const char *service); - -/*! Check if the service is allowed to be hot/cold plugged - * @param service to check - * @return true if allowed, otherwise false */ -bool rc_service_plugable (const char *service); - -/*! Resolves a service name to its full path. - * @param service to check - * @return pointer to full path of service */ -char *rc_service_resolve (const char *service); - -/*! Schedule a service to be started when another service starts - * @param service that starts the scheduled service when started - * @param service_to_start service that will be started */ -bool rc_service_schedule_start (const char *service, - const char *service_to_start); -/*! Return a NULL terminated list of services that are scheduled to start - * when the given service has started - * @param service to check - * @return NULL terminated list of services scheduled to start */ -char **rc_services_scheduled_by (const char *service); - -/*! Clear the list of services scheduled to be started by this service - * @param service to clear - * @return true if no errors, otherwise false */ -bool rc_service_schedule_clear (const char *service); - -/*! Checks if a service in in a state - * @param service to check - * @return state of the service */ -rc_service_state_t rc_service_state (const char *service); - -/*! Start a service - * @param service to start - * @return pid of the service starting process */ -pid_t rc_service_start (const char *service); - -/*! Stop a service - * @param service to stop - * @return pid of service stopping process */ -pid_t rc_service_stop (const char *service); - -/*! Check if the service started the daemon - * @param service to check - * @param exec to check - * @param indx of the daemon (optional - 1st daemon, 2nd daemon, etc) - * @return true if started by this service, otherwise false */ -bool rc_service_started_daemon (const char *service, const char *exec, - int indx); - -/*! Return a saved value for a service - * @param service to check - * @param option to load - * @return saved value */ -char *rc_service_value_get (const char *service, const char *option); - -/*! Save a persistent value for a service - * @param service to save for - * @param option to save - * @param value of the option - * @return true if saved, otherwise false */ -bool rc_service_value_set (const char *service, const char *option, - const char *value); - -/*! List the services in a runlevel - * @param runlevel to list - * @return NULL terminated list of services */ -char **rc_services_in_runlevel (const char *runlevel); - -/*! List the services in a state - * @param state to list - * @return NULL terminated list of services */ -char **rc_services_in_state (rc_service_state_t state); - -/*! List the services shceduled to start when this one does - * @param service to check - * @return NULL terminated list of services */ -char **rc_services_scheduled (const char *service); - -/*! Checks that all daemons started with start-stop-daemon by the service - * are still running. - * @param service to check - * @return true if all daemons started are still running, otherwise false */ -bool rc_service_daemons_crashed (const char *service); - -/*! @name Dependency options - * These options can change the services found by the rc_get_depinfo and - * rc_get_depends functions. */ -/*! Trace provided services */ -#define RC_DEP_TRACE 0x01 -/*! Only use services added to runlevels */ -#define RC_DEP_STRICT 0x02 -/*! Runlevel is starting */ -#define RC_DEP_START 0x04 -/*! Runlevel is stopping */ -#define RC_DEP_STOP 0x08 - -/*! @name Dependencies - * We analyse each init script and cache the resultant dependency tree. - * This tree can be accessed using the below functions. */ - -#ifndef _IN_LIBRC -/* Handles to internal structures */ -typedef void *rc_depinfo_t; -#endif - -/*! Update the cached dependency tree if it's older than any init script, - * its configuration file or an external configuration file the init script - * has specified. - * @return true if successful, otherwise false */ -bool rc_deptree_update (void); - -/*! Check if the cached dependency tree is older than any init script, - * its configuration file or an external configuration file the init script - * has specified. - * @return true if it needs updating, otherwise false */ -bool rc_deptree_update_needed (void); - -/*! Load the cached dependency tree and return a pointer to it. - * This pointer should be freed with rc_deptree_free when done. - * @return pointer to the dependency tree */ -rc_depinfo_t *rc_deptree_load (void); - -/*! List the depend for the type of service - * @param deptree to search - * @param type to use (keywords, etc) - * @param service to check - * @return NULL terminated list of services in order */ -char **rc_deptree_depend (const rc_depinfo_t *deptree, - const char *type, const char *service); - -/*! List all the services in order that the given services have - * for the given types and options. - * @param deptree to search - * @param types to use (ineed, iuse, etc) - * @param services to check - * @param options to pass - * @return NULL terminated list of services in order */ -char **rc_deptree_depends (const rc_depinfo_t *deptree, - const char *const *types, - const char *const *services, const char *runlevel, - int options); - -/*! List all the services that should be stoppned and then started, in order, - * for the given runlevel, including sysinit and boot services where - * approriate. - * @param deptree to search - * @param runlevel to change into - * @param options to pass - * @return NULL terminated list of services in order */ -char **rc_deptree_order (const rc_depinfo_t *deptree, const char *runlevel, - int options); - -/*! Free a deptree and its information - * @param deptree to free */ -void rc_deptree_free (rc_depinfo_t *deptree); - -/*! @name Plugins - * For each plugin loaded we will call rc_plugin_hook with the below - * enum and either the runlevel name or service name. - * - * Plugins are called when rc does something. This does not indicate an - * end result and the plugin should use the above functions to query things - * like service status. - * - * The service hooks have extra ones - now and done. This is because after - * start_in we may start other services before we start the service in - * question. now shows we really will start the service now and done shows - * when we have done it as may start scheduled services at this point. */ -/*! Points at which a plugin can hook into RC */ -typedef enum -{ - RC_HOOK_RUNLEVEL_STOP_IN = 1, - RC_HOOK_RUNLEVEL_STOP_OUT = 4, - RC_HOOK_RUNLEVEL_START_IN = 5, - RC_HOOK_RUNLEVEL_START_OUT = 8, - /*! We send the abort if an init script requests we abort and drop - * into single user mode if system not fully booted */ - RC_HOOK_ABORT = 99, - RC_HOOK_SERVICE_STOP_IN = 101, - RC_HOOK_SERVICE_STOP_NOW = 102, - RC_HOOK_SERVICE_STOP_DONE = 103, - RC_HOOK_SERVICE_STOP_OUT = 104, - RC_HOOK_SERVICE_START_IN = 105, - RC_HOOK_SERVICE_START_NOW = 106, - RC_HOOK_SERVICE_START_DONE = 107, - RC_HOOK_SERVICE_START_OUT = 108 -} rc_hook_t; - -/*! Plugin entry point - * @param hook point - * @param name of runlevel or service - * @return 0 for success otherwise -1 */ -int rc_plugin_hook (rc_hook_t hook, const char *name); - -/*! Plugins should write FOO=BAR to this fd to set any environment - * variables they wish. Variables should be separated by NULLs. */ -extern FILE *rc_environ_fd; - -/*! @name Configuration - * These functions help to deal with shell based configuration files */ -/*! Return a NULL terminated list of non comment lines from a file. */ -char **rc_config_list (const char *file); - -/*! Return a NULL terminated list of key=value lines from a file. */ -char **rc_config_load (const char *file); - -/*! Return the value of the entry from a key=value list. */ -char *rc_config_value (char **list, const char *entry); - -/*! Check if a variable is a boolean and return it's value. - * If variable is not a boolean then we set errno to be ENOENT when it does - * not exist or EINVAL if it's not a boolean. - * @param variable to check - * @return true if it matches true, yes or 1, false if otherwise. */ -bool rc_yesno (const char *variable); - -/*! @name String List functions - * Handy functions for dealing with string arrays of char **. - * It's safe to assume that any function here that uses char ** is a string - * list that can be manipulated with the below functions. Every string list - * should be released with a call to rc_strlist_free. */ - -/*! Duplicate the item, add it to end of the list and return a pointer to it. - * @param list to add the item too - * @param item to add. - * @return pointer to newly added item */ -char *rc_strlist_add (char ***list, const char *item); - -/*! If the item does not exist in the list, duplicate it, add it to the - * list and then return a pointer to it. - * @param list to add the item too - * @param item to add. - * @return pointer to newly added item */ -char *rc_strlist_addu (char ***list, const char *item); - -/*! Duplicate the item, add it to the list at the point based on locale and - * then return a pointer to it. - * @param list to add the item too - * @param item to add. - * @return pointer to newly added item */ -char *rc_strlist_addsort (char ***list, const char *item); - -/*! Duplicate the item, add it to the list at the point based on C locale and - * then return a pointer to it. - * @param list to add the item too - * @param item to add. - * @return pointer to newly added item */ -char *rc_strlist_addsortc (char ***list, const char *item); - -/*! If the item does not exist in the list, duplicate it, add it to the - * list based on locale and then return a pointer to it. - * @param list to add the item too - * @param item to add. - * @return pointer to newly added item */ -char *rc_strlist_addsortu (char ***list, const char *item); - -/*! Free the item and remove it from the list. Return 0 on success otherwise -1. - * @param list to add the item too - * @param item to add. - * @return true on success, otherwise false */ -bool rc_strlist_delete (char ***list, const char *item); - -/*! Moves the contents of list2 onto list1, so list2 is effectively emptied. - * Returns a pointer to the last item on the new list. - * @param list1 to append to - * @param list2 to move from - * @return pointer to the last item on the list */ -char *rc_strlist_join (char ***list1, char **list2); - -/*! Reverses the contents of the list. - * @param list to reverse */ -void rc_strlist_reverse (char **list); - -/*! Frees each item on the list and the list itself. - * @param list to free */ -void rc_strlist_free (char **list); - -/*! Concatenate paths adding '/' if needed. The resultant pointer should be - * freed when finished with. - * @param path1 starting path - * @param paths NULL terminated list of paths to add - * @return pointer to the new path */ -char *rc_strcatpaths (const char *path1, const char *paths, ...) SENTINEL; - -/*! Find processes based on criteria. - * All of these are optional. - * pid overrides anything else. - * If both exec and cmd are given then we ignore exec. - * @param exec to check for - * @param cmd to check for - * @param uid to check for - * @param pid to check for - * @return NULL terminated list of pids */ -pid_t *rc_find_pids (const char *exec, const char *cmd, - uid_t uid, pid_t pid); - -#endif diff --git a/src/rc.map b/src/rc.map deleted file mode 100644 index e5f8ee34..00000000 --- a/src/rc.map +++ /dev/null @@ -1,58 +0,0 @@ -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/_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]" }; + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include + +/* 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 +#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 +#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 + +#if defined(__DragonFly__) || defined(__FreeBSD__) || \ + defined(__NetBSD__) || defined(__OpenBSD__) +#define BSD +#include +#include +#include +#elif defined (__linux__) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#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 +#include + +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +# include +#else +# include +#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 + +#ifdef __linux__ +#include +#include +#endif + +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +# include +#else +# include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_PAM +#include + +/* 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 | */ + parse_schedule (optarg, sig); + break; + + case 'S': /* --start */ + start = true; + break; + + case 'b': /* --background */ + background = true; + break; + + case 'u': /* --user | */ + case 'c': /* --chuid | */ + { + 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 | */ + { + 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 */ + cmd = optarg; + break; + + case 'o': /* --oknodo */ + oknodo = true; + break; + + case 'p': /* --pidfile */ + pidfile = optarg; + break; + + case 's': /* --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 */ + 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 diff --git a/src/runscript.c b/src/runscript.c deleted file mode 100644 index 1385bb02..00000000 --- a/src/runscript.c +++ /dev/null @@ -1,1311 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef __linux__ -# include -#else -# include -#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/start-stop-daemon.c b/src/start-stop-daemon.c deleted file mode 100644 index bf03dbec..00000000 --- a/src/start-stop-daemon.c +++ /dev/null @@ -1,1070 +0,0 @@ -/* - 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_PAM -#include - -/* 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 | */ - parse_schedule (optarg, sig); - break; - - case 'S': /* --start */ - start = true; - break; - - case 'b': /* --background */ - background = true; - break; - - case 'u': /* --user | */ - case 'c': /* --chuid | */ - { - 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 | */ - { - 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 */ - cmd = optarg; - break; - - case 'o': /* --oknodo */ - oknodo = true; - break; - - case 'p': /* --pidfile */ - pidfile = optarg; - break; - - case 's': /* --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 */ - 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/start-stop-daemon.pam b/src/start-stop-daemon.pam deleted file mode 100644 index 860a3d52..00000000 --- a/src/start-stop-daemon.pam +++ /dev/null @@ -1,6 +0,0 @@ -#%PAM-1.0 - -auth sufficient pam_rootok.so -account required pam_permit.so -password required pam_deny.so -session optional pam_limits.so diff --git a/src/termcap.mk b/src/termcap.mk new file mode 100644 index 00000000..7b5058db --- /dev/null +++ b/src/termcap.mk @@ -0,0 +1,3 @@ +LIBTERMCAP?= -ltermcap +CPPFLAGS+= -DHAVE_TERMCAP +LDADD+= ${LIBTERMCAP} diff --git a/subdir.mk b/subdir.mk new file mode 100644 index 00000000..03bec8c3 --- /dev/null +++ b/subdir.mk @@ -0,0 +1,25 @@ +# Recursive rules +# Adapted from FreeBSDs bsd.subdir.mk +_+_ ?= + +ECHODIR ?= true +_SUBDIR = @${_+_}for x in ${SUBDIR}; do \ + if test -d $$x; then \ + ${ECHODIR} "===> ${DIRPRFX}$$x ($@)"; \ + cd $$x; \ + ${MAKE} $@ DIRPRFX=${DIRPRFX}$$x/ || exit $$?; \ + cd ..; \ + fi; \ + if test -d $$x.${OS}; then \ + ${ECHODIR} "===> ${DIRPRFX}$$x.${OS} ($@)"; \ + cd $$x.${OS}; \ + ${MAKE} $@ DIRPRFX=${DIRPRFX}$$x/ || exit $$?; \ + cd ..; \ + fi; \ +done + +all: + $(_SUBDIR) +clean: + $(_SUBDIR) +install: + $(_SUBDIR) -- cgit v1.2.3