summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile153
-rw-r--r--src/einfo.h83
-rw-r--r--src/env-update.c247
-rw-r--r--src/env_whitelist48
-rw-r--r--src/fstabinfo.c146
-rw-r--r--src/libeinfo.c877
-rw-r--r--src/librc-daemon.c600
-rw-r--r--src/librc-depend.c838
-rw-r--r--src/librc-misc.c750
-rw-r--r--src/librc-strlist.c141
-rw-r--r--src/librc.c773
-rw-r--r--src/mountinfo.c246
-rw-r--r--src/rc-depend.c120
-rw-r--r--src/rc-misc.h34
-rw-r--r--src/rc-plugin.c119
-rw-r--r--src/rc-plugin.h15
-rw-r--r--src/rc-status.c142
-rw-r--r--src/rc-update.c162
-rw-r--r--src/rc.c1174
-rw-r--r--src/rc.h180
-rw-r--r--src/runscript.c1097
-rw-r--r--src/splash.c130
-rw-r--r--src/start-stop-daemon.c1047
-rw-r--r--src/start-stop-daemon.pam6
-rw-r--r--src/strlist.h24
25 files changed, 9152 insertions, 0 deletions
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..131b4d5
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,153 @@
+# Copyright 1999-2007 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+CC ?= gcc
+
+CFLAGS ?= -Wall -O2 -pipe
+CFLAGS += -pedantic -std=c99 \
+ -Wall -Wextra -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
+
+# Early GCC versions don't support these flags, so you may need to comment
+# this line out
+CFLAGS += -Wsequence-point -Wextra -Wdeclaration-after-statement
+
+# For debugging. -Werror is pointless due to ISO C issues with dlsym
+#CFLAGS += -ggdb
+
+DESTDIR =
+LIB = lib
+
+LIBEINFOSOVER = 0
+LIBEINFOSO = libeinfo.so.$(LIBRCSOVER)
+LIBEINFOOBJS= libeinfo.o
+
+LIBRCSOVER = 0
+LIBRCSO = librc.so.$(LIBRCSOVER)
+LIBRCOBJS= librc.o librc-depend.o librc-daemon.o librc-misc.o librc-strlist.o
+
+LIB_TARGETS = $(LIBEINFOSO) $(LIBRCSO)
+BIN_TARGETS = rc-status
+SBIN_TARGETS = env-update fstabinfo mountinfo \
+ rc rc-depend rc-update runscript start-stop-daemon
+SYS_WHITELIST = env_whitelist
+
+TARGET = $(LIB_TARGETS) $(BIN_TARGETS) $(SBIN_TARGETS)
+
+RCLINKS = einfon einfo ewarnn ewarn eerrorn eerror ebegin eend ewend \
+ eindent eoutdent eflush color_terminal \
+ veinfo vewarn vebegin veend vewend veindent veoutdent \
+ service_starting service_inactive service_started \
+ service_stopping service_stopped \
+ service_inactive service_wasinactive \
+ service_coldplugged \
+ mark_service_starting mark_service_inactive mark_service_started \
+ mark_service_stopping mark_service_stopped \
+ mark_service_inactive mark_service_wasinactive \
+ mark_service_coldplugged \
+ get_options save_options \
+ is_runlevel_start is_runlevel_stop service_started_daemon
+
+# Quick hack to make my life easier on BSD and Linux
+ifeq ($(OS),)
+OS=$(shell uname -s)
+ifneq ($(OS),Linux)
+OS=BSD
+endif
+endif
+
+ifeq ($(OS),Linux)
+LDLIBS_RC = -ldl
+LDLIBS_RS = -ldl
+# Shouldn't need this, but it's the easiest workaround for silly
+# Linux headers that don't work with -std=c99
+override CFLAGS += -D_GNU_SOURCE
+endif
+ifeq ($(OS),BSD)
+override LDLIBS += -lkvm
+endif
+
+HAVE_PAM =
+ifdef HAVE_PAM
+CFLAGS_SSD = -DHAVE_PAM
+LDLIBS_SSD = -lpam
+endif
+
+# We also define _BSD_SOURCE so both Linux and the BSDs get a few
+# handy functions which makes our lives a lot easier
+override CFLAGS += -DLIBDIR=\"$(LIB)\"
+
+# IMPORTANT!!!
+# Remove this when releasing as it's a security risk
+# However, this does save us using libtool when we're testing
+# NOTE: The toplevel Makefile for baselayout will automatically
+# disable then when doing `make dist`
+##override LDFLAGS += -Wl,-rpath .
+
+all: $(TARGET)
+
+$(LIBEINFOOBJS): CFLAGS += -fPIC
+$(LIBEINFOSO): LDLIBS =
+$(LIBEINFOSO): $(LIBEINFOOBJS)
+ $(CC) -fPIC -shared -Wl,-soname,$(LIBEINFOSO) -o $(LIBEINFOSO) $(LIBEINFOOBJS)
+ ln -sf $(LIBEINFOSO) libeinfo.so
+
+$(LIBRCOBJS): CFLAGS += -fPIC
+$(LIBRCSO): $(LIBRCOBJS)
+ $(CC) -fPIC -shared -Wl,-soname,$(LIBRCSO) -o $(LIBRCSO) $(LIBRCOBJS)
+ ln -sf $(LIBRCSO) librc.so
+
+splash: CFLAGS += -fPIC
+splash: splash.o
+ $(CC) -fPIC -shared -Wl,-soname,splash.so -o splash.so splash.o
+
+env-update: $(LIBEINFOSO) $(LIBRCSO) env-update.o
+
+fstabinfo: $(LIBEINFOSO) fstabinfo.o
+
+mountinfo: $(LIBEINFOSO) $(LIBRCSO) mountinfo.o
+
+rc-depend: $(LIBEINFOSO) $(LIBRCSO) rc-depend.o
+
+rc-status: $(LIBEINFOSO) $(LIBRCSO) rc-status.o
+
+rc-update: $(LIBEINFOSO) $(LIBRCSO) rc-update.o
+
+rc: LDLIBS += $(LDLIBS_RC)
+rc: $(LIBEINFOSO) $(LIBRCSO) rc-plugin.o rc.o
+
+runscript: LDLIBS += $(LDLIBS_RS)
+runscript: $(LIBEINFOSO) $(LIBRCSO) rc-plugin.o runscript.o
+
+start-stop-daemon: CFLAGS += $(CFLAGS_SSD)
+start-stop-daemon: LDLIBS += $(LDLIBS_SSD)
+start-stop-daemon: $(LIBEINFOSO) $(LIBRCSO) start-stop-daemon.o
+
+links: rc
+ for x in $(RCLINKS) $(RCPRIVLINKS); do ln -sf rc $$x; done
+
+install: $(TARGET)
+ install -m 0755 -d $(DESTDIR)/$(LIB)
+ install -m 0755 $(LIB_TARGETS) $(DESTDIR)/$(LIB)
+ ln -sf $(LIBEINFOSO) $(DESTDIR)/$(LIB)/libeinfo.so
+ ln -sf $(LIBRCSO) $(DESTDIR)/$(LIB)/librc.so
+ install -m 0755 -d $(DESTDIR)/usr/include
+ install -m 0644 einfo.h rc.h $(DESTDIR)/usr/include
+ install -m 0755 -d $(DESTDIR)/bin
+ install -m 0755 $(BIN_TARGETS) $(DESTDIR)/bin
+ install -m 0755 -d $(DESTDIR)/sbin
+ install -m 0755 $(SBIN_TARGETS) $(DESTDIR)/sbin
+ install -m 0755 -d $(DESTDIR)/$(LIB)/rcscripts/conf.d
+ install -m 0644 $(SYS_WHITELIST) $(DESTDIR)/$(LIB)/rcscripts/conf.d
+ install -m 0755 -d $(DESTDIR)/$(LIB)/rcscripts/bin
+ for x in $(RCLINKS); do ln -sf $(DESTDIR)/sbin/rc $(DESTDIR)/$(LIB)/rcscripts/bin/$$x; done
+ if test "$(HAVE_PAM)" != "" ; then \
+ install -m 0755 -d $(DESTDIR)/etc/pam.d ; \
+ install -m 0644 start-stop-daemon.pam $(DESTDIR)/etc/pam.d/start-stop-daemon ; \
+ fi
+
+clean:
+ rm -f $(TARGET) $(RCLINKS) $(RCPRIVLINKS)
+ rm -f *.o *~ *.core *.so
diff --git a/src/einfo.h b/src/einfo.h
new file mode 100644
index 0000000..7ab52af
--- /dev/null
+++ b/src/einfo.h
@@ -0,0 +1,83 @@
+/*
+ rc.h
+ Header file for external applications to get RC information.
+ Copyright 2007 Gentoo Foundation
+ Released under the GPLv2
+ */
+
+#ifndef __EINFO_H__
+#define __EINFO_H__
+
+#ifdef __GNUC__
+# define EINFO_PRINTF(_one, _two) __attribute__ ((__format__ (__printf__, _one, _two)))
+# define EINFO_XPRINTF(_one, _two) __attribute__ ((__noreturn__, __format__ (__printf__, _one, _two)))
+#endif
+
+#include <sys/types.h>
+#include <stdbool.h>
+
+typedef enum
+{
+ einfo_good,
+ einfo_warn,
+ einfo_bad,
+ einfo_hilite,
+ einfo_bracket,
+ einfo_normal
+} einfo_color_t;
+
+/* Colour codes used by the below functions. */
+#define EINFO_GOOD "\033[32;01m"
+#define EINFO_WARN "\033[33;01m"
+#define EINFO_BAD "\033[31;01m"
+#define EINFO_HILITE "\033[36;01m"
+#define EINFO_BRACKET "\033[34;01m"
+#define EINFO_NORMAL "\033[0m"
+
+/* Handy macros to easily use the above in a custom manner */
+#define PEINFO_GOOD if (colour_terminal ()) printf (EINFO_GOOD)
+#define PEINFO_WARN if (colour_terminal ()) printf (EINFO_WARN)
+#define PEINFO_BAD if (colour_terminal ()) printf (EINFO_BAD)
+#define PEINFO_HILITE if (colour_terminal ()) printf (EINFO_HILITE)
+#define PEINFO_BRACKET if (colour_terminal ()) printf (EINFO_BRACKET)
+#define PEINFO_NORMAL if (colour_terminal ()) printf (EINFO_NORMAL)
+
+/* We work out if the terminal supports colour or not through the use
+ of the TERM env var. We cache the reslt in a static bool, so
+ subsequent calls are very fast. */
+/* The n suffix means that a newline is NOT appended to the string
+ The v prefix means that we only print it when RC_VERBOSE=yes */
+bool colour_terminal (void);
+int einfon (const char *fmt, ...) EINFO_PRINTF (1, 2);
+int ewarnn (const char *fmt, ...) EINFO_PRINTF (1, 2);
+int eerrorn (const char *fmt, ...) EINFO_PRINTF (1, 2);
+int einfo (const char *fmt, ...) EINFO_PRINTF(1, 2);
+int ewarn (const char *fmt, ...) EINFO_PRINTF (1, 2);
+void ewarnx (const char *fmt, ...) EINFO_XPRINTF (1,2);
+int eerror (const char *fmt, ...) EINFO_PRINTF (1,2);
+void eerrorx (const char *fmt, ...) EINFO_XPRINTF (1,2);
+int ebegin (const char *fmt, ...) EINFO_PRINTF (1, 2);
+int eend (int retval, const char *fmt, ...) EINFO_PRINTF (2, 3);
+int ewend (int retval, const char *fmt, ...) EINFO_PRINTF (2, 3);
+void ebracket (int col, einfo_color_t color, const char *msg);
+void eindent (void);
+void eoutdent (void);
+
+int veinfon (const char *fmt, ...) EINFO_PRINTF (1, 2);
+int vewarnn (const char *fmt, ...) EINFO_PRINTF (1, 2);
+int vebeginn (const char *fmt, ...) EINFO_PRINTF (1, 2);
+int veendn (int retval, const char *fmt, ...) EINFO_PRINTF (2, 3);
+int vewendn (int retval, const char *fmt, ...) EINFO_PRINTF (2, 3);
+int veinfo (const char *fmt, ...) EINFO_PRINTF (1, 2);
+int vewarn (const char *fmt, ...) EINFO_PRINTF (1, 2);
+int vebegin (const char *fmt, ...) EINFO_PRINTF (1, 2);
+int veend (int retval, const char *fmt, ...) EINFO_PRINTF (2, 3);
+int vewend (int retval, const char *fmt, ...) EINFO_PRINTF (2, 3);
+void veindent (void);
+void veoutdent (void);
+
+/* If RC_EBUFFER is set, then we buffer all the above commands.
+ As such, we need to flush the buffer when done. */
+void eflush(void);
+
+#endif
diff --git a/src/env-update.c b/src/env-update.c
new file mode 100644
index 0000000..c04974b
--- /dev/null
+++ b/src/env-update.c
@@ -0,0 +1,247 @@
+/*
+ env-update
+ Create /etc/profile.env (sh), /etc/csh.env from /etc/env.d
+ Run ldconfig as required
+
+ Copyright 2007 Gentoo Foundation
+ Released under the GPLv2
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "strlist.h"
+
+#define ENVDIR "/etc/env.d"
+#define PROFILE_ENV "/etc/profile.env"
+#define CSH_ENV "/etc/csh.env"
+#define LDSOCONF "/etc/ld.so.conf"
+
+#define NOTICE "# THIS FILE IS AUTOMATICALLY GENERATED BY env-update.\n" \
+ "# DO NOT EDIT THIS FILE. CHANGES TO STARTUP PROFILES\n" \
+ "# GO INTO %s NOT %s\n\n"
+
+#define LDNOTICE "# ld.so.conf autogenerated by env-update; make all\n" \
+ "# changes to contents of /etc/env.d directory\n"
+
+static const char *specials[] =
+{
+ "ADA_INCLUDE_PATH",
+ "ADA_OBJECTS_PATH",
+ "CLASSPATH",
+ "INFOPATH",
+ "KDEDIRS",
+ "LDPATH",
+ "MANPATH",
+ "PATH",
+ "PKG_CONFIG_PATH",
+ "PRELINK_PATH",
+ "PRELINK_PATH_MASK",
+ "PYTHONPATH",
+ "ROOTPATH",
+ NULL
+};
+
+static const char *special_spaces[] =
+{
+ "CONFIG_PROTECT",
+ "CONFIG_PROTECT_MASK",
+ NULL,
+};
+
+static char *applet = NULL;
+
+int main (int argc, char **argv)
+{
+ char **files = rc_ls_dir (NULL, ENVDIR, 0);
+ char *file;
+ char **envs = NULL;
+ char *env;
+ int i = 0;
+ FILE *fp;
+ bool ld = true;
+ char *ldent;
+ char **ldents = NULL;
+ int nents = 0;
+
+ applet = argv[0];
+
+ if (! files)
+ eerrorx ("%s: no files in " ENVDIR " to process", applet);
+
+ STRLIST_FOREACH (files, file, i)
+ {
+ char *path = rc_strcatpaths (ENVDIR, file, NULL);
+ char **entries = NULL;
+ char *entry;
+ int j;
+
+ if (! rc_is_dir (path))
+ entries = rc_get_config (NULL, path);
+ free (path);
+
+ STRLIST_FOREACH (entries, entry, j)
+ {
+ char *tmpent = rc_xstrdup (entry);
+ char *value = tmpent;
+ char *var = strsep (&value, "=");
+ int k;
+ bool isspecial = false;
+ bool isspecial_spaced = false;
+ bool replaced = false;
+
+ for (k = 0; special_spaces[k]; k++)
+ if (strcmp (special_spaces[k], var) == 0)
+ {
+ isspecial = true;
+ isspecial_spaced = true;
+ break;
+ }
+
+ if (! isspecial)
+ {
+ for (k = 0; specials[k]; k++)
+ if (strcmp (specials[k], var) == 0)
+ {
+ isspecial = true;
+ break;
+ }
+ }
+
+ /* Skip blank vars */
+ if (isspecial &&
+ (! value || strlen (value)) == 0)
+ {
+ free (tmpent);
+ continue;
+ }
+
+ STRLIST_FOREACH (envs, env, k)
+ {
+ char *tmpenv = rc_xstrdup (env);
+ char *tmpvalue = tmpenv;
+ char *tmpentry = strsep (&tmpvalue, "=");
+
+ if (strcmp (tmpentry, var) == 0)
+ {
+ if (isspecial)
+ {
+ envs[k - 1] = rc_xrealloc (envs[k - 1],
+ strlen (envs[k - 1]) +
+ strlen (entry) + 1);
+ sprintf (envs[k - 1] + strlen (envs[k - 1]),
+ "%s%s", isspecial_spaced ? " " : ":", value);
+ }
+ else
+ {
+ free (envs[k - 1]);
+ envs[k - 1] = strdup (entry);
+ }
+ replaced = true;
+ }
+ free (tmpenv);
+
+ if (replaced)
+ break;
+ }
+
+ if (! replaced)
+ envs = rc_strlist_addsort (envs, entry);
+
+ free (tmpent);
+ }
+ }
+
+ if ((fp = fopen (PROFILE_ENV, "w")) == NULL)
+ eerrorx ("%s: fopen `%s': %s", applet, PROFILE_ENV, strerror (errno));
+ fprintf (fp, NOTICE, "/etc/profile", PROFILE_ENV);
+ STRLIST_FOREACH (envs, env, i)
+ {
+ char *tmpent = rc_xstrdup (env);
+ char *value = tmpent;
+ char *var = strsep (&value, "=");
+ if (strcmp (var, "LDPATH") != 0)
+ fprintf (fp, "export %s='%s'\n", var, value);
+ free (tmpent);
+ }
+ fclose (fp);
+
+ if ((fp = fopen (CSH_ENV, "w")) == NULL)
+ eerrorx ("%s: fopen `%s': %s", applet, PROFILE_ENV, strerror (errno));
+ fprintf (fp, NOTICE, "/etc/csh.cshrc", PROFILE_ENV);
+ STRLIST_FOREACH (envs, env, i)
+ {
+ char *tmpent = rc_xstrdup (env);
+ char *value = tmpent;
+ char *var = strsep (&value, "=");
+ if (strcmp (var, "LDPATH") != 0)
+ fprintf (fp, "setenv %s '%s'\n", var, value);
+ free (tmpent);
+ }
+ fclose (fp);
+
+ ldent = rc_get_config_entry (envs, "LDPATH");
+
+ if (! ldent ||
+ (argc > 1 && argv[1] && strcmp (argv[1], "--no-ldconfig") == 0))
+ {
+ free (envs);
+ return (EXIT_SUCCESS);
+ }
+
+ while ((file = strsep (&ldent, ":")))
+ {
+ if (strlen (file) == 0)
+ continue;
+
+ ldents = rc_strlist_add (ldents, file);
+ nents++;
+ }
+
+ /* Update ld.so.conf only if different */
+ if (rc_exists (LDSOCONF))
+ {
+ char **lines = rc_get_list (NULL, LDSOCONF);
+ char *line;
+ ld = false;
+ STRLIST_FOREACH (lines, line, i)
+ if (i > nents || strcmp (line, ldents[i - 1]) != 0)
+ {
+ ld = true;
+ break;
+ }
+ if (i - 1 != nents)
+ ld = true;
+ }
+
+ if (ld)
+ {
+ int retval = 0;
+
+ if ((fp = fopen (LDSOCONF, "w")) == NULL)
+ eerrorx ("%s: fopen `%s': %s", applet, LDSOCONF, strerror (errno));
+ fprintf (fp, LDNOTICE);
+ STRLIST_FOREACH (ldents, ldent, i)
+ fprintf (fp, "%s\n", ldent);
+ fclose (fp);
+
+#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+ ebegin ("Regenerating /var/run/ld-elf.so.hints");
+ retval = system ("/sbin/ldconfig -elf -i '" LDSOCONF "'");
+#else
+ ebegin ("Regenerating /etc/ld.so.cache");
+ retval = system ("/sbin/ldconfig");
+#endif
+ eend (retval, NULL);
+ }
+
+ return(EXIT_SUCCESS);
+}
diff --git a/src/env_whitelist b/src/env_whitelist
new file mode 100644
index 0000000..ca21935
--- /dev/null
+++ b/src/env_whitelist
@@ -0,0 +1,48 @@
+# System environment whitelist for rc-system
+# See /etc/conf.d/env_whitelist for details.
+
+#
+# Internal variables needed for operation of rc-system
+# NB: Do not modify below this line if you do not know what you are doing!!
+#
+
+# Hotplug
+IN_HOTPLUG
+
+# RC network script support
+IN_BACKGROUND
+RC_INTERFACE_KEEP_CONFIG
+
+# Default shell stuff
+PATH
+SHELL
+USER
+HOME
+TERM
+
+# Language variables
+LANG
+LC_CTYPE
+LC_NUMERIC
+LC_TIME
+LC_COLLATE
+LC_MONETARY
+LC_MESSAGES
+LC_PAPER
+LC_NAME
+LC_ADDRESS
+LC_TELEPHONE
+LC_MEASUREMENT
+LC_IDENTIFICATION
+LC_ALL
+
+# From /sbin/init
+INIT_HALT
+INIT_VERSION
+RUNLEVEL
+PREVLEVEL
+CONSOLE
+
+# Allow this through too so we can prefer stuff in /lib when shutting down
+# or going to single mode.
+LD_LIBRARY_PATH
diff --git a/src/fstabinfo.c b/src/fstabinfo.c
new file mode 100644
index 0000000..6f45cc7
--- /dev/null
+++ b/src/fstabinfo.c
@@ -0,0 +1,146 @@
+/*
+ fstabinfo.c
+ Gets information about /etc/fstab.
+
+ Copyright 2007 Gentoo Foundation
+ */
+
+#include <errno.h>
+#include <libgen.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Yay for linux and it's non liking of POSIX functions.
+ Okay, we could use getfsent but the man page says use getmntent instead
+ AND we don't have getfsent on uclibc or dietlibc for some odd reason. */
+#ifdef __linux__
+#define HAVE_GETMNTENT
+#include <mntent.h>
+#define GET_ENT getmntent (fp)
+#define GET_ENT_FILE(_name) getmntfile (fp, _name)
+#define END_ENT endmntent (fp)
+#define ENT_DEVICE(_ent) ent->mnt_fsname
+#define ENT_FILE(_ent) ent->mnt_dir
+#define ENT_TYPE(_ent) ent->mnt_type
+#define ENT_OPTS(_ent) ent->mnt_opts
+#define ENT_PASS(_ent) ent->mnt_passno
+#else
+#define HAVE_GETFSENT
+#include <fstab.h>
+#define GET_ENT getfsent ()
+#define GET_ENT_FILE(_name) getfsfile (_name)
+#define END_ENT endfsent ()
+#define ENT_DEVICE(_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 "einfo.h"
+
+#ifdef HAVE_GETMNTENT
+static struct mntent *getmntfile (FILE *fp, const char *file)
+{
+ struct mntent *ent;
+
+ while ((ent = getmntent (fp)))
+ if (strcmp (file, ent->mnt_dir) == 0)
+ return (ent);
+
+ return (NULL);
+}
+#endif
+
+int main (int argc, char **argv)
+{
+ int i;
+#ifdef HAVE_GETMNTENT
+ FILE *fp;
+ struct mntent *ent;
+#else
+ struct fstab *ent;
+#endif
+ int result = EXIT_FAILURE;
+ char *p;
+ char *token;
+ int n = 0;
+
+ for (i = 1; i < argc; i++)
+ {
+#ifdef HAVE_GETMNTENT
+ fp = setmntent ("/etc/fstab", "r");
+#endif
+
+ if (strcmp (argv[i], "--fstype") == 0 && i + 1 < argc)
+ {
+ i++;
+ p = argv[i];
+ while ((token = strsep (&p, ",")))
+ while ((ent = GET_ENT))
+ if (strcmp (token, ENT_TYPE (ent)) == 0)
+ printf ("%s\n", ENT_FILE (ent));
+ result = EXIT_SUCCESS;
+ }
+
+ if (strcmp (argv[i], "--mount-cmd") == 0 && i + 1 < argc)
+ {
+ i++;
+ if ((ent = GET_ENT_FILE (argv[i])) == NULL)
+ continue;
+ printf ("-o %s -t %s %s %s\n", ENT_OPTS (ent), ENT_TYPE (ent),
+ ENT_DEVICE (ent), ENT_FILE (ent));
+ result = EXIT_SUCCESS;
+ }
+
+ if (strcmp (argv[i], "--opts") == 0 && i + 1 < argc)
+ {
+ i++;
+ if ((ent = GET_ENT_FILE (argv[i])) == NULL)
+ continue;
+ printf ("%s\n", ENT_OPTS (ent));
+ result = EXIT_SUCCESS;
+ }
+
+ if (strcmp (argv[i], "--passno") == 0 && i + 1 < argc)
+ {
+ i++;
+ switch (argv[i][0])
+ {
+ case '=':
+ case '<':
+ case '>':
+ if (sscanf (argv[i] + 1, "%d", &n) != 1)
+ eerrorx ("%s: invalid passno %s", argv[0], argv[i] + 1);
+
+ while ((ent = GET_ENT))
+ {
+ if (((argv[i][0] == '=' && n == ENT_PASS (ent)) ||
+ (argv[i][0] == '<' && n > ENT_PASS (ent)) ||
+ (argv[i][0] == '>' && n < ENT_PASS (ent))) &&
+ strcmp (ENT_FILE (ent), "none") != 0)
+ printf ("%s\n", ENT_FILE (ent));
+ }
+
+ default:
+ if ((ent = GET_ENT_FILE (argv[i])) == NULL)
+ continue;
+ printf ("%d\n", ENT_PASS (ent));
+ result = EXIT_SUCCESS;
+ }
+ }
+
+ END_ENT;
+
+ if (result != EXIT_SUCCESS)
+ {
+ eerror ("%s: unknown option `%s'", basename (argv[0]), argv[i]);
+ break;
+ }
+
+ }
+
+ exit (result);
+}
+
diff --git a/src/libeinfo.c b/src/libeinfo.c
new file mode 100644
index 0000000..e0faf98
--- /dev/null
+++ b/src/libeinfo.c
@@ -0,0 +1,877 @@
+/*
+ einfo.c
+ Gentoo informational functions
+ Copyright 2007 Gentoo Foundation
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+
+/* Incase we cannot work out how many columns from ioctl, supply a default */
+#define DEFAULT_COLS 80
+
+#define OK "ok"
+#define NOT_OK "!!"
+
+#define CHECK_VERBOSE if (! is_env ("RC_VERBOSE", "yes")) return 0
+
+/* Number of spaces for an indent */
+#define INDENT_WIDTH 2
+
+/* How wide can the indent go? */
+#define INDENT_MAX 40
+
+#define EBUFFER_LOCK RC_SVCDIR "ebuffer/.lock"
+
+/* A cheat sheet of colour capable terminals
+ This is taken from DIR_COLORS from GNU coreutils
+ We embed it here as we shouldn't depend on coreutils */
+static const char *colour_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
+};
+
+static bool is_env (const char *var, const char *val)
+{
+ char *v;
+
+ if (! var)
+ return (false);
+
+ v = getenv (var);
+ if (! v)
+ return (val == NULL ? true : false);
+
+ return (strcasecmp (v, val) == 0 ? true : false);
+}
+
+bool colour_terminal (void)
+{
+ static int in_colour = -1;
+ int i = 0;
+ char *term;
+
+ if (in_colour == 0)
+ return (false);
+ if (in_colour == 1)
+ return (true);
+
+ term = getenv ("TERM");
+ /* If $TERM isn't set then the chances are we're in single user mode */
+ if (! term)
+ return (true);
+
+ while (colour_terms[i])
+ {
+ if (strcmp (colour_terms[i], term) == 0)
+ {
+ in_colour = 1;
+ return (true);
+ }
+ i++;
+ }
+
+ in_colour = 0;
+ return (false);
+}
+
+static int get_term_columns (void)
+{
+#ifdef TIOCGSIZE /* BSD */
+ struct ttysize ts;
+
+ if (ioctl(0, TIOCGSIZE, &ts) == 0)
+ return (ts.ts_cols);
+#elif TIOCGWINSZ /* Linux */
+ struct winsize ws;
+
+ if (ioctl(0, TIOCGWINSZ, &ws) == 0)
+ return (ws.ws_col);
+#endif
+
+ return (DEFAULT_COLS);
+}
+
+static int ebuffer (const char *cmd, int retval, const char *fmt, va_list ap)
+{
+ char *file = getenv ("RC_EBUFFER");
+ FILE *fp;
+ char buffer[RC_LINEBUFFER];
+ int l = 1;
+
+ if (! file || ! cmd || strlen (cmd) < 4)
+ return (0);
+
+ if (! (fp = fopen (file, "a")))
+ {
+ fprintf (stderr, "fopen `%s': %s\n", file, strerror (errno));
+ return (0);
+ }
+
+ fprintf (fp, "%s %d ", cmd, retval);
+
+ if (fmt)
+ {
+ l = vsnprintf (buffer, sizeof (buffer), fmt, ap);
+ fprintf (fp, "%d %s\n", l, buffer);
+ }
+ else
+ fprintf (fp, "0\n");
+
+ fclose (fp);
+ return (l);
+}
+
+typedef struct func
+{
+ const char *name;
+ int (*efunc) (const char *fmt, ...);
+ int (*eefunc) (int retval, const char *fmt, ...);
+ void (*eind) (void);
+} func_t;
+
+static const func_t funcmap[] = {
+ { "einfon", &einfon, NULL, NULL },
+ { "ewarnn", &ewarnn, NULL, NULL},
+ { "eerrorn", &eerrorn, NULL, NULL},
+ { "einfo", &einfo, NULL, NULL },
+ { "ewarn", &ewarn, NULL, NULL },
+ { "eerror", &eerror, NULL, NULL },
+ { "ebegin", &ebegin, NULL, NULL },
+ { "eend", NULL, &eend, NULL },
+ { "ewend", NULL, &ewend, NULL },
+ { "eindent", NULL, NULL, &eindent },
+ { "eoutdent", NULL, NULL, &eoutdent },
+ { "veinfon", &veinfon, NULL, NULL },
+ { "vewarnn", &vewarnn, NULL, NULL },
+ { "veinfo", &veinfo, NULL, NULL },
+ { "vewarn", &vewarn, NULL, NULL },
+ { "vebegin", &vebegin, NULL, NULL },
+ { "veend", NULL, &veend, NULL },
+ { "vewend", NULL, &vewend, NULL },
+ { "veindent" ,NULL, NULL, &veindent },
+ { "veoutdent", NULL, NULL, &veoutdent },
+ { NULL, NULL, NULL, NULL },
+};
+
+void eflush (void)
+{
+ FILE *fp;
+ char *file = getenv ("RC_EBUFFER");
+ char buffer[RC_LINEBUFFER];
+ char *cmd;
+ int retval = 0;
+ int length = 0;
+ char *token;
+ char *p;
+ struct stat buf;
+ pid_t pid;
+ char newfile[PATH_MAX];
+ int i = 1;
+
+ if (! file|| (stat (file, &buf) != 0))
+ {
+ errno = 0;
+ return;
+ }
+
+ /* Find a unique name for our file */
+ while (true)
+ {
+ snprintf (newfile, sizeof (newfile), "%s.%d", file, i);
+ if (stat (newfile, &buf) != 0)
+ {
+ if (rename (file, newfile))
+ fprintf (stderr, "rename `%s' `%s': %s\n", file, newfile,
+ strerror (errno));
+ break;
+ }
+ i++;
+ }
+
+ /* We fork a child process here so we don't hold anything up */
+ if ((pid = fork ()) == -1)
+ {
+ fprintf (stderr, "fork: %s", strerror (errno));
+ return;
+ }
+
+ if (pid != 0)
+ return;
+
+ /* Spin until we can lock the ebuffer */
+ while (true)
+ {
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 20000;
+ select (0, NULL, NULL, NULL, &tv);
+ errno = 0;
+ if (link (newfile, EBUFFER_LOCK) == 0)
+ break;
+ if (errno != EEXIST)
+ fprintf (stderr, "link `%s' `%s': %s\n", newfile, EBUFFER_LOCK,
+ strerror (errno));
+ }
+
+ if (! (fp = fopen (newfile, "r")))
+ {
+ fprintf (stderr, "fopen `%s': %s\n", newfile, strerror (errno));
+ return;
+ }
+
+ unsetenv ("RC_EBUFFER");
+
+ memset (buffer, 0, RC_LINEBUFFER);
+ while (fgets (buffer, RC_LINEBUFFER, fp))
+ {
+ i = strlen (buffer) - 1;
+ if (i < 1)
+ continue;
+
+ if (buffer[i] == '\n')
+ buffer[i] = 0;
+
+ p = buffer;
+ cmd = strsep (&p, " ");
+ token = strsep (&p, " ");
+ if (sscanf (token, "%d", &retval) != 1)
+ {
+ fprintf (stderr, "eflush `%s': not a number", token);
+ continue;
+ }
+ token = strsep (&p, " ");
+ if (sscanf (token, "%d", &length) != 1)
+ {
+ fprintf (stderr, "eflush `%s': not a number", token);
+ continue;
+ }
+
+ i = 0;
+ while (funcmap[i].name)
+ {
+ if (strcmp (funcmap[i].name, cmd) == 0)
+ {
+ if (funcmap[i].efunc)
+ {
+ if (p)
+ funcmap[i].efunc ("%s", p);
+ else
+ funcmap[i].efunc (NULL, NULL);
+ }
+ else if (funcmap[i].eefunc)
+ {
+ if (p)
+ funcmap[i].eefunc (retval, "%s", p);
+ else
+ funcmap[i].eefunc (retval, NULL, NULL);
+ }
+ else if (funcmap[i].eind)
+ funcmap[i].eind ();
+ else
+ fprintf (stderr, "eflush `%s': no function defined\n", cmd);
+ break;
+ }
+ i++;
+ }
+
+ if (! funcmap[i].name)
+ fprintf (stderr, "eflush `%s': invalid function\n", cmd);
+ }
+ fclose (fp);
+
+ if (unlink (EBUFFER_LOCK))
+ fprintf (stderr, "unlink `%s': %s", EBUFFER_LOCK, strerror (errno));
+
+ if (unlink (newfile))
+ fprintf (stderr, "unlink `%s': %s", newfile, strerror (errno));
+
+ _exit (EXIT_SUCCESS);
+}
+
+#define EBUFFER(_cmd, _retval, _fmt, _ap) \
+{ \
+ int _i = ebuffer (_cmd, _retval, _fmt, _ap); \
+ if (_i) \
+ return (_i); \
+}
+
+static void elog (int level, const char *fmt, va_list ap)
+{
+ char *e = getenv ("RC_ELOG");
+
+ if (e)
+ {
+ closelog ();
+ openlog (e, LOG_PID, LOG_DAEMON);
+ vsyslog (level, fmt, ap);
+ }
+}
+static int _eindent (FILE *stream)
+{
+ char *env = getenv ("RC_EINDENT");
+ 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));
+}
+
+#define VEINFON(_file, _colour) \
+ if (colour_terminal ()) \
+ fprintf (_file, " " _colour "*" EINFO_NORMAL " "); \
+ else \
+ fprintf (_file, " * "); \
+ retval += _eindent (_file); \
+ retval += vfprintf (_file, fmt, ap) + 3; \
+ if (colour_terminal ()) \
+ fprintf (_file, "\033[K");
+
+static int _veinfon (const char *fmt, va_list ap)
+{
+ int retval = 0;
+
+ VEINFON (stdout, EINFO_GOOD);
+ return (retval);
+}
+
+static int _vewarnn (const char *fmt, va_list ap)
+{
+ int retval = 0;
+
+ VEINFON (stdout, EINFO_WARN);
+ return (retval);
+}
+
+static int _veerrorn (const char *fmt, va_list ap)
+{
+ int retval = 0;
+
+ VEINFON (stderr, EINFO_BAD);
+ return (retval);
+}
+
+int einfon (const char *fmt, ...)
+{
+ int retval;
+ va_list ap;
+
+ if (! fmt || is_env ("RC_QUIET", "yes"))
+ return (0);
+
+ va_start (ap, fmt);
+ if (! (retval = ebuffer ("einfon", 0, fmt, ap)))
+ retval = _veinfon (fmt, ap);
+ va_end (ap);
+
+ return (retval);
+}
+
+int ewarnn (const char *fmt, ...)
+{
+ int retval;
+ va_list ap;
+
+ if (! fmt || is_env ("RC_QUIET", "yes"))
+ return (0);
+
+ va_start (ap, fmt);
+ if (! (retval = ebuffer ("ewarnn", 0, fmt, ap)))
+ retval = _vewarnn (fmt, ap);
+ va_end (ap);
+
+ return (retval);
+}
+
+int eerrorn (const char *fmt, ...)
+{
+ int retval;
+ va_list ap;
+
+ va_start (ap, fmt);
+ if (! (retval = ebuffer ("eerrorn", 0, fmt, ap)))
+ retval = _veerrorn (fmt, ap);
+ va_end (ap);
+
+ return (retval);
+}
+
+int einfo (const char *fmt, ...)
+{
+ int retval;
+ va_list ap;
+
+ if (! fmt || is_env ("RC_QUIET", "yes"))
+ return (0);
+
+ va_start (ap, fmt);
+ if (! (retval = ebuffer ("einfo", 0, fmt, ap)))
+ {
+ retval = _veinfon (fmt, ap);
+ retval += printf ("\n");
+ }
+ va_end (ap);
+
+ return (retval);
+}
+
+int ewarn (const char *fmt, ...)
+{
+ int retval;
+ va_list ap;
+
+ if (! fmt || is_env ("RC_QUIET", "yes"))
+ return (0);
+
+ va_start (ap, fmt);
+ elog (LOG_WARNING, fmt, ap);
+ if (! (retval = ebuffer ("ewarn", 0, fmt, ap)))
+ {
+ retval = _vewarnn (fmt, ap);
+ retval += printf ("\n");
+ }
+ va_end (ap);
+
+ return (retval);
+}
+
+void ewarnx (const char *fmt, ...)
+{
+ int retval;
+ va_list ap;
+
+ if (fmt && ! is_env ("RC_QUIET", "yes"))
+ {
+ va_start (ap, fmt);
+ elog (LOG_WARNING, fmt, ap);
+ retval = _vewarnn (fmt, ap);
+ va_end (ap);
+ retval += printf ("\n");
+ }
+ exit (EXIT_FAILURE);
+}
+
+int eerror (const char *fmt, ...)
+{
+ int retval;
+ va_list ap;
+
+ if (! fmt)
+ return (0);
+
+ va_start (ap, fmt);
+ elog (LOG_ERR, fmt, ap);
+ retval = _veerrorn (fmt, ap);
+ va_end (ap);
+ retval += fprintf (stderr, "\n");
+
+ return (retval);
+}
+
+void eerrorx (const char *fmt, ...)
+{
+ va_list ap;
+
+ if (fmt)
+ {
+ va_start (ap, fmt);
+ elog (LOG_ERR, fmt, ap);
+ _veerrorn (fmt, ap);
+ va_end (ap);
+ printf ("\n");
+ }
+ exit (EXIT_FAILURE);
+}
+
+int ebegin (const char *fmt, ...)
+{
+ int retval;
+ va_list ap;
+
+ if (! fmt || is_env ("RC_QUIET", "yes"))
+ return (0);
+
+ va_start (ap, fmt);
+ if ((retval = ebuffer ("ebegin", 0, fmt, ap)))
+ {
+ va_end (ap);
+ return (retval);
+ }
+
+ retval = _veinfon (fmt, ap);
+ va_end (ap);
+ retval += printf (" ...");
+ if (colour_terminal ())
+ retval += printf ("\n");
+
+ return (retval);
+}
+
+static void _eend (int col, einfo_color_t color, const char *msg)
+{
+ FILE *fp = stdout;
+ int i;
+ int cols;
+
+ if (! msg)
+ return;
+
+ if (color == einfo_bad)
+ fp = stderr;
+
+ cols = get_term_columns () - (strlen (msg) + 6);
+
+ if (cols > 0 && colour_terminal ())
+ {
+ fprintf (fp, "\033[A\033[%dC %s[ ", cols, EINFO_BRACKET);
+ switch (color)
+ {
+ case einfo_good:
+ fprintf (fp, EINFO_GOOD);
+ break;
+ case einfo_warn:
+ fprintf (fp, EINFO_WARN);
+ break;
+ case einfo_bad:
+ fprintf (fp, EINFO_BAD);
+ break;
+ case einfo_hilite:
+ fprintf (fp, EINFO_HILITE);
+ break;
+ case einfo_bracket:
+ fprintf (fp, EINFO_BRACKET);
+ break;
+ case einfo_normal:
+ fprintf (fp, EINFO_NORMAL);
+ break;
+ }
+ fprintf (fp, "%s%s ]%s\n", msg, EINFO_BRACKET, EINFO_NORMAL);
+ }
+ else
+ {
+ for (i = -1; i < cols - col; i++)
+ fprintf (fp, " ");
+ fprintf (fp, "[ %s ]\n", msg);
+ }
+}
+
+static int _do_eend (const char *cmd, int retval, const char *fmt, va_list ap)
+{
+ int col = 0;
+ FILE *fp;
+
+ if (ebuffer (cmd, retval, fmt, ap))
+ return (retval);
+
+ if (fmt && retval != 0)
+ {
+ if (strcmp (cmd, "ewend") == 0)
+ {
+ col = _vewarnn (fmt, ap);
+ fp = stdout;
+ }
+ else
+ {
+ col = _veerrorn (fmt, ap);
+ fp = stderr;
+ }
+ if (colour_terminal ())
+ fprintf (fp, "\n");
+ }
+
+ _eend (col, retval == 0 ? einfo_good : einfo_bad, retval == 0 ? OK : NOT_OK);
+ return (retval);
+}
+
+int eend (int retval, const char *fmt, ...)
+{
+ va_list ap;
+
+ if (is_env ("RC_QUIET", "yes"))
+ return (retval);
+
+ va_start (ap, fmt);
+ _do_eend ("eend", retval, fmt, ap);
+ va_end (ap);
+
+ return (retval);
+}
+
+int ewend (int retval, const char *fmt, ...)
+{
+ va_list ap;
+
+ if (is_env ("RC_QUIET", "yes"))
+ return (retval);
+
+ va_start (ap, fmt);
+ _do_eend ("ewend", retval, fmt, ap);
+ va_end (ap);
+
+ return (retval);
+}
+
+void ebracket (int col, einfo_color_t color, const char *msg)
+{
+ _eend (col, color, msg);
+}
+
+void eindent (void)
+{
+ char *env = getenv ("RC_EINDENT");
+ int amount = 0;
+ char num[10];
+
+ if (ebuffer ("eindent", 0, NULL, NULL))
+ return;
+
+ 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 ("RC_EINDENT", num, 1);
+}
+
+void eoutdent (void)
+{
+ char *env = getenv ("RC_EINDENT");
+ int amount = 0;
+ char num[10];
+
+ if (ebuffer ("eoutdent", 0, NULL, NULL))
+ return;
+
+ if (! env)
+ return;
+
+ errno = 0;
+ amount = strtol (env, NULL, 0);
+ if (errno != 0)
+ amount = 0;
+ else
+ amount -= INDENT_WIDTH;
+
+ if (amount <= 0)
+ unsetenv ("RC_EINDENT");
+ else
+ {
+ snprintf (num, 10, "%08d", amount);
+ setenv ("RC_EINDENT", num, 1);
+ }
+}
+
+int veinfon (const char *fmt, ...)
+{
+ int retval;
+ va_list ap;
+
+ CHECK_VERBOSE;
+
+ if (! fmt)
+ return (0);
+
+ va_start (ap, fmt);
+ if (! (retval = ebuffer ("veinfon", 0, fmt, ap)))
+ retval = _veinfon (fmt, ap);
+ va_end (ap);
+
+ return (retval);
+}
+
+int vewarnn (const char *fmt, ...)
+{
+ int retval;
+ va_list ap;
+
+ CHECK_VERBOSE;
+
+ if (! fmt)
+ return (0);
+
+ va_start (ap, fmt);
+ if (! (retval = ebuffer ("vewarnn", 0, fmt, ap)))
+ retval = _vewarnn (fmt, ap);
+ va_end (ap);
+
+ return (retval);
+}
+
+int veinfo (const char *fmt, ...)
+{
+ int retval;
+ va_list ap;
+
+ CHECK_VERBOSE;
+
+ if (! fmt)
+ return (0);
+
+ va_start (ap, fmt);
+ if (! (retval = ebuffer ("veinfo", 0, fmt, ap)))
+ {
+ retval = _veinfon (fmt, ap);
+ retval += printf ("\n");
+ }
+ va_end (ap);
+
+ return (retval);
+}
+
+int vewarn (const char *fmt, ...)
+{
+ int retval;
+ va_list ap;
+
+ CHECK_VERBOSE;
+
+ if (! fmt)
+ return (0);
+
+ va_start (ap, fmt);
+ if (! (retval = ebuffer ("vewarn", 0, fmt, ap)))
+ {
+ retval = _vewarnn (fmt, ap);
+ retval += printf ("\n");
+ }
+ va_end (ap);
+ retval += printf ("\n");
+
+ return (retval);
+}
+
+int vebegin (const char *fmt, ...)
+{
+ int retval;
+ va_list ap;
+
+ CHECK_VERBOSE;
+
+ if (! fmt)
+ return (0);
+
+ va_start (ap, fmt);
+ if (! (retval = ebuffer ("vewarn", 0, fmt, ap)))
+ {
+ retval = _veinfon (fmt, ap);
+ retval += printf (" ...");
+ if (colour_terminal ())
+ retval += printf ("\n");
+ }
+ va_end (ap);
+
+ return (retval);
+}
+
+int veend (int retval, const char *fmt, ...)
+{
+ va_list ap;
+
+ CHECK_VERBOSE;
+
+ va_start (ap, fmt);
+ _do_eend ("veend", retval, fmt, ap);
+ va_end (ap);
+
+ return (retval);
+}
+
+int vewend (int retval, const char *fmt, ...)
+{
+ va_list ap;
+
+ CHECK_VERBOSE;
+
+ va_start (ap, fmt);
+ _do_eend ("vewend", retval, fmt, ap);
+ va_end (ap);
+
+ return (retval);
+}
+
+void veindent (void)
+{
+ if (is_env ("RC_VERBOSE", "yes"))
+ eindent ();
+}
+
+void veoutdent (void)
+{
+ if (is_env ("RC_VERBOSE", "yes"))
+ eoutdent ();
+}
diff --git a/src/librc-daemon.c b/src/librc-daemon.c
new file mode 100644
index 0000000..02d0d93
--- /dev/null
+++ b/src/librc-daemon.c
@@ -0,0 +1,600 @@
+/*
+ librc-daemon
+ Finds PID for given daemon criteria
+ Copyright 2007 Gentoo Foundation
+ Released under the GPLv2
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#if defined(__FreeBSD__) || defined(__NetBSD__) || defined (__OpenBSD__)
+#include <sys/param.h>
+#include <sys/user.h>
+#include <sys/sysctl.h>
+#include <kvm.h>
+#include <limits.h>
+#endif
+
+#ifndef __linux__
+#include <libgen.h>
+#endif
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "strlist.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;
+ int foundany = false;
+ pid_t p;
+ pid_t *pids = NULL;
+ char buffer[PATH_MAX];
+ struct stat sb;
+ pid_t runscript_pid = 0;
+ char *pp;
+
+ if ((procdir = opendir ("/proc")) == NULL)
+ eerrorx ("opendir `/proc': %s", strerror (errno));
+
+ /*
+ 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;
+ foundany = true;
+
+ if (runscript_pid != 0 && runscript_pid == p)
+ continue;
+
+ if (pid != 0 && pid != p)
+ continue;
+
+ if (uid)
+ {
+ snprintf (buffer, sizeof (buffer), "/proc/%d", pid);
+ 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;
+
+ pids = realloc (pids, sizeof (pid_t) * (npids + 2));
+ if (! pids)
+ eerrorx ("memory exhausted");
+
+ pids[npids] = p;
+ pids[npids + 1] = 0;
+ npids++;
+ }
+ closedir (procdir);
+
+ if (! foundany)
+ eerrorx ("nothing in /proc");
+
+ return (pids);
+}
+
+#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+
+# if defined(__FreeBSD__)
+# define _KINFO_PROC kinfo_proc
+# define _KVM_GETPROCS kvm_getprocs
+# 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 _KINFO_PROC kinfo_proc2
+# define _KVM_GETPROCS kvm_getprocs2
+# 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;
+ int npids = 0;
+
+ if ((kd = kvm_openfiles (NULL, NULL, NULL, O_RDONLY, errbuf)) == NULL)
+ eerrorx ("kvm_open: %s", errbuf);
+
+ kp = _KVM_GETPROCS (kd, KERN_PROC_PROC, 0, &processes);
+ 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;
+ }
+
+ pids = realloc (pids, sizeof (pid_t) * (npids + 2));
+ if (! pids)
+ eerrorx ("memory exhausted");
+
+ pids[npids] = p;
+ pids[npids + 1] = 0;
+ npids++;
+ }
+ kvm_close(kd);
+
+ return (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[RC_LINEBUFFER];
+ char *ffile = rc_strcatpaths (path, file, NULL);
+ FILE *fp;
+ int lc = 0;
+ int m = 0;
+
+ if (! rc_exists (ffile))
+ {
+ free (ffile);
+ return (false);
+ }
+
+ if ((fp = fopen (ffile, "r")) == NULL)
+ {
+ eerror ("fopen `%s': %s", ffile, strerror (errno));
+ free (ffile);
+ return (false);
+ }
+
+ if (! mname)
+ m += 10;
+ if (! mpidfile)
+ m += 100;
+
+ memset (buffer, 0, sizeof (buffer));
+ 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;
+ }
+ fclose (fp);
+ free (ffile);
+
+ return (m == 111 ? true : false);
+}
+
+void rc_set_service_daemon (const char *service, const char *exec,
+ const char *name, const char *pidfile,
+ bool started)
+{
+ char *dirpath = rc_strcatpaths (RC_SVCDIR, "daemons", basename (service), NULL);
+ char **files = NULL;
+ char *file;
+ char *ffile = NULL;
+ int i;
+ char *mexec;
+ char *mname;
+ char *mpidfile;
+ int nfiles = 0;
+
+ if (! exec && ! name && ! pidfile)
+ return;
+
+ if (exec)
+ {
+ i = strlen (exec) + 6;
+ mexec = rc_xmalloc (sizeof (char *) * i);
+ snprintf (mexec, i, "exec=%s", exec);
+ }
+ else
+ mexec = strdup ("exec=");
+
+ if (name)
+ {
+ i = strlen (name) + 6;
+ mname = rc_xmalloc (sizeof (char *) * i);
+ snprintf (mname, i, "name=%s", name);
+ }
+ else
+ mname = strdup ("name=");
+
+ if (pidfile)
+ {
+ i = strlen (pidfile) + 9;
+ mpidfile = rc_xmalloc (sizeof (char *) * i);
+ snprintf (mpidfile, i, "pidfile=%s", pidfile);
+ }
+ else
+ mpidfile = strdup ("pidfile=");
+
+ /* Regardless, erase any existing daemon info */
+ if (rc_is_dir (dirpath))
+ {
+ char *oldfile = NULL;
+ files = rc_ls_dir (NULL, dirpath, 0);
+ STRLIST_FOREACH (files, file, i)
+ {
+ ffile = rc_strcatpaths (dirpath, file, NULL);
+ nfiles++;
+
+ if (! oldfile)
+ {
+ if (_match_daemon (dirpath, file, mexec, mname, mpidfile))
+ {
+ unlink (ffile);
+ oldfile = ffile;
+ nfiles--;
+ }
+ }
+ else
+ {
+ rename (ffile, oldfile);
+ free (oldfile);
+ oldfile = ffile;
+ }
+ }
+ if (ffile)
+ free (ffile);
+ free (files);
+ }
+
+ /* Now store our daemon info */
+ if (started)
+ {
+ char buffer[10];
+ FILE *fp;
+
+ if (! rc_is_dir (dirpath))
+ if (mkdir (dirpath, 0755) != 0)
+ eerror ("mkdir `%s': %s", dirpath, strerror (errno));
+
+ snprintf (buffer, sizeof (buffer), "%03d", nfiles + 1);
+ file = rc_strcatpaths (dirpath, buffer, NULL);
+ if ((fp = fopen (file, "w")) == NULL)
+ eerror ("fopen `%s': %s", file, strerror (errno));
+ else
+ {
+ fprintf (fp, "%s\n%s\n%s\n", mexec, mname, mpidfile);
+ fclose (fp);
+ }
+ free (file);
+ }
+
+ free (mexec);
+ free (mname);
+ free (mpidfile);
+ free (dirpath);
+}
+
+bool rc_service_started_daemon (const char *service, const char *exec,
+ int indx)
+{
+ char *dirpath;
+ char *file;
+ int i;
+ char *mexec;
+ bool retval = false;
+
+ if (! service || ! exec)
+ return (false);
+
+ dirpath = rc_strcatpaths (RC_SVCDIR, "daemons", basename (service), NULL);
+ if (! rc_is_dir (dirpath))
+ {
+ free (dirpath);
+ return (false);
+ }
+
+ i = strlen (exec) + 6;
+ mexec = rc_xmalloc (sizeof (char *) * i);
+ snprintf (mexec, i, "exec=%s", exec);
+
+ if (indx > 0)
+ {
+ file = rc_xmalloc (sizeof (char *) * 10);
+ snprintf (file, sizeof (file), "%03d", indx);
+ retval = _match_daemon (dirpath, file, mexec, NULL, NULL);
+ free (file);
+ }
+ else
+ {
+ char **files = rc_ls_dir (NULL, dirpath, 0);
+ STRLIST_FOREACH (files, file, i)
+ {
+ retval = _match_daemon (dirpath, file, mexec, NULL, NULL);
+ if (retval)
+ break;
+ }
+ free (files);
+ }
+
+ free (mexec);
+ return (retval);
+}
+
+bool rc_service_daemons_crashed (const char *service)
+{
+ char *dirpath;
+ char **files;
+ char *file;
+ char *path;
+ int i;
+ FILE *fp;
+ char buffer[RC_LINEBUFFER];
+ 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 (service), NULL);
+ if (! rc_is_dir (dirpath))
+ {
+ free (dirpath);
+ return (false);
+ }
+
+ memset (buffer, 0, sizeof (buffer));
+ files = rc_ls_dir (NULL, dirpath, 0);
+ STRLIST_FOREACH (files, file, i)
+ {
+ path = rc_strcatpaths (dirpath, file, NULL);
+ fp = fopen (path, "r");
+ free (path);
+ if (! fp)
+ {
+ eerror ("fopen `%s': %s", file, strerror (errno));
+ continue;
+ }
+
+ 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 = strdup (p);
+ }
+ else if (strcmp (token, "name") == 0)
+ {
+ if (name)
+ free (name);
+ name = strdup (p);
+ }
+ else if (strcmp (token, "pidfile") == 0)
+ {
+ if (pidfile)
+ free (pidfile);
+ pidfile = strdup (p);
+ }
+ }
+ fclose (fp);
+
+ pid = 0;
+ if (pidfile)
+ {
+ if (! rc_exists (pidfile))
+ {
+ retval = true;
+ break;
+ }
+
+ if ((fp = fopen (pidfile, "r")) == NULL)
+ {
+ eerror ("fopen `%s': %s", pidfile, strerror (errno));
+ retval = true;
+ break;
+ }
+
+ if (fscanf (fp, "%d", &pid) != 1)
+ {
+ eerror ("no pid found in `%s'", pidfile);
+ fclose (fp);
+ retval = true;
+ break;
+ }
+
+ fclose (fp);
+ free (pidfile);
+ pidfile = NULL;
+ }
+
+ if ((pids = rc_find_pids (exec, name, 0, pid)) == NULL)
+ {
+ retval = true;
+ break;
+ }
+ free (pids);
+
+ if (exec)
+ {
+ free (exec);
+ exec = NULL;
+ }
+ if (name)
+ {
+ free (name);
+ name = NULL;
+ }
+ }
+
+ if (exec)
+ {
+ free (exec);
+ exec = NULL;
+ }
+ if (name)
+ {
+ free (name);
+ name = NULL;
+ }
+
+ free (dirpath);
+ rc_strlist_free (files);
+
+ return (retval);
+}
diff --git a/src/librc-depend.c b/src/librc-depend.c
new file mode 100644
index 0000000..0da93aa
--- /dev/null
+++ b/src/librc-depend.c
@@ -0,0 +1,838 @@
+/*
+ librc-depend
+ rc service dependency and ordering
+ Copyright 2006-2007 Gentoo Foundation
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "strlist.h"
+
+#define GENDEP RC_LIBDIR "/sh/gendepends.sh"
+
+/* 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_free_deptree (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;
+ }
+}
+
+rc_depinfo_t *rc_load_deptree (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;
+
+ /* Update our deptree, but only if we need too */
+ rc_update_deptree (false);
+
+ 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 = rc_xmalloc (sizeof (rc_depinfo_t));
+ depinfo = deptree;
+ }
+ else
+ {
+ depinfo->next = rc_xmalloc (sizeof (rc_depinfo_t));
+ depinfo = depinfo->next;
+ }
+ memset (depinfo, 0, sizeof (rc_depinfo_t));
+ depinfo->service = strdup (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 = rc_xmalloc (sizeof (rc_deptype_t));
+ deptype = depinfo->depends;
+ memset (deptype, 0, sizeof (rc_deptype_t));
+ }
+ else
+ if (strcmp (deptype->type, type) != 0)
+ {
+ deptype->next = rc_xmalloc (sizeof (rc_deptype_t));
+ deptype = deptype->next;
+ memset (deptype, 0, sizeof (rc_deptype_t));
+ }
+
+ if (! deptype->type)
+ deptype->type = strdup (type);
+
+ deptype->services = rc_strlist_addsort (deptype->services, e);
+ }
+ fclose (fp);
+
+ return (deptree);
+}
+
+rc_depinfo_t *rc_get_depinfo (rc_depinfo_t *deptree, const char *service)
+{
+ rc_depinfo_t *di;
+
+ if (! deptree || ! service)
+ return (NULL);
+
+ for (di = deptree; di; di = di->next)
+ if (strcmp (di->service, service) == 0)
+ return (di);
+
+ return (NULL);
+}
+
+rc_deptype_t *rc_get_deptype (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);
+}
+
+static bool valid_service (const char *runlevel, const char *service)
+{
+ return ((strcmp (runlevel, RC_LEVEL_BOOT) != 0 &&
+ rc_service_in_runlevel (service, RC_LEVEL_BOOT)) ||
+ rc_service_in_runlevel (service, runlevel) ||
+ rc_service_state (service, rc_service_coldplugged) ||
+ rc_service_state (service, rc_service_started));
+}
+
+static bool get_provided1 (const char *runlevel, struct lhead *providers,
+ rc_deptype_t *deptype,
+ const char *level, bool coldplugged,
+ bool started, bool inactive)
+{
+ char *service;
+ int i;
+ bool retval = false;
+
+ STRLIST_FOREACH (deptype->services, service, i)
+ {
+ bool ok = true;
+ if (level)
+ ok = rc_service_in_runlevel (service, level);
+ else if (coldplugged)
+ ok = (rc_service_state (service, rc_service_coldplugged) &&
+ ! rc_service_in_runlevel (service, runlevel) &&
+ ! rc_service_in_runlevel (service, RC_LEVEL_BOOT));
+
+ if (! ok)
+ continue;
+
+ if (started)
+ ok = (rc_service_state (service, rc_service_starting) ||
+ rc_service_state (service, rc_service_started) ||
+ rc_service_state (service, rc_service_stopping));
+ else if (inactive)
+ ok = rc_service_state (service, rc_service_inactive);
+
+ if (! ok)
+ continue;
+
+ retval = true;
+ providers->list = 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 (rc_depinfo_t *deptree, 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 = rc_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)
+ providers.list = rc_strlist_add (providers.list, service);
+
+ return (providers.list);
+ }
+
+ /* If we're strict, then only use what we have in our runlevel */
+ if (options & RC_DEP_STRICT)
+ {
+ STRLIST_FOREACH (dt->services, service, i)
+ if (rc_service_in_runlevel (service, runlevel))
+ providers.list = 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, true, false))
+ { DO }
+ if (get_provided1 (runlevel, &providers, dt, runlevel, false, false, true))
+ { DO }
+ if (get_provided1 (runlevel, &providers, dt, runlevel, false, false, false))
+ return (providers.list);
+
+ /* Check coldplugged started services */
+ if (get_provided1 (runlevel, &providers, dt, NULL, true, true, false))
+ { DO }
+
+ /* Check bootlevel if we're not in it */
+ if (strcmp (runlevel, RC_LEVEL_BOOT) != 0)
+ {
+ if (get_provided1 (runlevel, &providers, dt, RC_LEVEL_BOOT, false, true, false))
+ { DO }
+ if (get_provided1 (runlevel, &providers, dt, RC_LEVEL_BOOT, false, false, true))
+ { DO }
+ }
+
+ /* Check coldplugged inactive services */
+ if (get_provided1 (runlevel, &providers, dt, NULL, true, false, true))
+ { DO }
+
+ /* Check manually started */
+ if (get_provided1 (runlevel, &providers, dt, NULL, false, true, false))
+ { DO }
+ if (get_provided1 (runlevel, &providers, dt, NULL, false, false, true))
+ { DO }
+
+ /* Nothing started then. OK, lets get the stopped services */
+ if (get_provided1 (runlevel, &providers, dt, NULL, true, false, false))
+ return (providers.list);
+ if ((strcmp (runlevel, RC_LEVEL_BOOT) != 0)
+ && (get_provided1 (runlevel, &providers, dt, RC_LEVEL_BOOT, false, false, false)))
+ return (providers.list);
+
+ /* Still nothing? OK, list all services */
+ STRLIST_FOREACH (dt->services, service, i)
+ providers.list = rc_strlist_add (providers.list, service);
+
+ return (providers.list);
+}
+
+static void visit_service (rc_depinfo_t *deptree, char **types,
+ struct lhead *sorted, struct lhead *visited,
+ rc_depinfo_t *depinfo,
+ const char *runlevel, int options)
+{
+ int i, j, k;
+ char *lp, *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 */
+ visited->list = rc_strlist_add (visited->list, depinfo->service);
+
+ STRLIST_FOREACH (types, item, i)
+ {
+ if ((dt = rc_get_deptype (depinfo, item)))
+ {
+ STRLIST_FOREACH (dt->services, service, j)
+ {
+ if (! options & RC_DEP_TRACE || strcmp (item, "iprovide") == 0)
+ {
+ sorted->list = rc_strlist_add (sorted->list, service);
+ continue;
+ }
+
+ di = rc_get_depinfo (deptree, service);
+ if ((provides = get_provided (deptree, di, runlevel, options)))
+ {
+ STRLIST_FOREACH (provides, lp, k)
+ {
+ di = rc_get_depinfo (deptree, lp);
+ if (di && (strcmp (item, "ineed") == 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 ||
+ 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 = rc_get_deptype (depinfo, "iprovide")))
+ {
+ STRLIST_FOREACH (dt->services, service, i)
+ {
+ if ((di = rc_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 (! rc_get_deptype (depinfo, "providedby"))
+ sorted->list = rc_strlist_add (sorted->list, depinfo->service);
+}
+
+char **rc_get_depends (rc_depinfo_t *deptree,
+ char **types, char **services,
+ const char *runlevel, int options)
+{
+ struct lhead sorted;
+ struct lhead visited;
+ rc_depinfo_t *di;
+ char *service;
+ int i;
+
+ if (! deptree || ! types || ! services)
+ return (NULL);
+
+ memset (&sorted, 0, sizeof (struct lhead));
+ memset (&visited, 0, sizeof (struct lhead));
+
+ STRLIST_FOREACH (services, service, i)
+ {
+ di = rc_get_depinfo (deptree, service);
+ visit_service (deptree, types, &sorted, &visited, di, runlevel, options);
+ }
+
+ rc_strlist_free (visited.list);
+ return (sorted.list);
+}
+
+char **rc_order_services (rc_depinfo_t *deptree, const char *runlevel,
+ int options)
+{
+ char **list = NULL;
+ char **types = NULL;
+ char **services = NULL;
+ bool reverse = false;
+
+ if (! runlevel)
+ return (NULL);
+
+ /* 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_ls_dir (list, RC_SVCDIR_STARTING, RC_LS_INITD);
+ list = rc_ls_dir (list, RC_SVCDIR_INACTIVE, RC_LS_INITD);
+ list = rc_ls_dir (list, RC_SVCDIR_STARTED, RC_LS_INITD);
+ reverse = true;
+ }
+ else
+ {
+ list = rc_services_in_runlevel (runlevel);
+
+ /* Add coldplugged services */
+ list = rc_ls_dir (list, RC_SVCDIR_COLDPLUGGED, RC_LS_INITD);
+
+ /* If we're not the boot runlevel then add that too */
+ if (strcmp (runlevel, RC_LEVEL_BOOT) != 0)
+ {
+ char *path = rc_strcatpaths (RC_RUNLEVELDIR, RC_LEVEL_BOOT, NULL);
+ list = rc_ls_dir (list, path, RC_LS_INITD);
+ free (path);
+ }
+ }
+
+ /* Now we have our lists, we need to pull in any dependencies
+ and order them */
+ types = rc_strlist_add (NULL, "ineed");
+ types = rc_strlist_add (types, "iuse");
+ types = rc_strlist_add (types, "iafter");
+ services = rc_get_depends (deptree, types, list, runlevel,
+ RC_DEP_STRICT | RC_DEP_TRACE | options);
+ rc_strlist_free (list);
+ rc_strlist_free (types);
+
+ if (reverse)
+ rc_strlist_reverse (services);
+
+ return (services);
+}
+
+static bool is_newer_than (const char *file, const char *target)
+{
+ struct stat buf;
+ int mtime;
+
+ if (stat (file, &buf) != 0 || buf.st_size == 0)
+ return (false);
+ mtime = buf.st_mtime;
+
+ if (stat (target, &buf) != 0)
+ return (false);
+
+ if (mtime < buf.st_mtime)
+ return (false);
+
+ if (rc_is_dir (target))
+ {
+ char **targets = rc_ls_dir (NULL, target, 0);
+ char *t;
+ int i;
+ bool newer = true;
+ STRLIST_FOREACH (targets, t, i)
+ {
+ char *path = rc_strcatpaths (target, t, NULL);
+ newer = is_newer_than (file, path);
+ free (path);
+ if (! newer)
+ break;
+ }
+ rc_strlist_free (targets);
+ return (newer);
+ }
+
+ return (true);
+}
+
+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",
+ RC_SVCDIR "ebuffer",
+ NULL
+};
+
+/* 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
+ */
+int rc_update_deptree (bool force)
+{
+ char *depends;
+ char *service;
+ char *type;
+ char *depend;
+ int retval = 0;
+ FILE *fp;
+ rc_depinfo_t *deptree;
+ rc_depinfo_t *depinfo;
+ rc_depinfo_t *di;
+ rc_depinfo_t *last_depinfo = NULL;
+ rc_deptype_t *deptype;
+ rc_deptype_t *dt;
+ rc_deptype_t *last_deptype = NULL;
+ char buffer[RC_LINEBUFFER];
+ int len;
+ int i;
+ int j;
+ int k;
+ bool already_added;
+
+ /* Create base directories if needed */
+ for (i = 0; depdirs[i]; i++)
+ if (! rc_is_dir (depdirs[i]))
+ if (mkdir (depdirs[i], 0755) != 0)
+ eerrorx ("mkdir `%s': %s", depdirs[i], strerror (errno));
+
+ if (! force)
+ if (is_newer_than (RC_DEPTREE, RC_INITDIR) &&
+ is_newer_than (RC_DEPTREE, RC_CONFDIR) &&
+ is_newer_than (RC_DEPTREE, "/etc/rc.conf"))
+ return 0;
+
+ ebegin ("Caching service dependencies");
+
+ /* 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")) == NULL)
+ eerrorx ("popen: %s", strerror (errno));
+
+ deptree = rc_xmalloc (sizeof (rc_depinfo_t));
+ memset (deptree, 0, sizeof (rc_depinfo_t));
+ 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 = rc_xmalloc (sizeof (rc_depinfo_t));
+ depinfo = last_depinfo->next;
+ }
+ memset (depinfo, 0, sizeof (rc_depinfo_t));
+ depinfo->service = strdup (service);
+ }
+
+ /* We may not have any depends */
+ if (! type || ! depends)
+ continue;
+
+ 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 = rc_xmalloc (sizeof (rc_deptype_t));
+ deptype = depinfo->depends;
+ }
+ else
+ {
+ last_deptype->next = rc_xmalloc (sizeof (rc_deptype_t));
+ deptype = last_deptype->next;
+ }
+ memset (deptype, 0, sizeof (rc_deptype_t));
+ deptype->type = strdup (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;
+
+ /* .sh files are not init scripts */
+ len = strlen (depend);
+ if (len > 2 &&
+ depend[len - 3] == '.' &&
+ depend[len - 2] == 's' &&
+ depend[len - 1] == 'h')
+ continue;
+
+ deptype->services = rc_strlist_addsort (deptype->services, depend);
+ }
+
+ }
+ pclose (fp);
+
+ /* Phase 3 - add our providors to the tree */
+ for (depinfo = deptree; depinfo; depinfo = depinfo->next)
+ {
+ if ((deptype = rc_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 = rc_xmalloc (sizeof (rc_depinfo_t));
+ di = last_depinfo->next;
+ memset (di, 0, sizeof (rc_depinfo_t));
+ di->service = strdup (service);
+ }
+ }
+ }
+
+ /* Phase 4 - backreference our depends */
+ for (depinfo = deptree; depinfo; depinfo = depinfo->next)
+ {
+ for (i = 0; deppairs[i].depend; i++)
+ {
+ deptype = rc_get_deptype (depinfo, deppairs[i].depend);
+ if (! deptype)
+ continue;
+
+ STRLIST_FOREACH (deptype->services, service, j)
+ {
+ di = rc_get_depinfo (deptree, service);
+ if (! di)
+ {
+ if (strcmp (deptype->type, "ineed") == 0)
+ {
+ eerror ("Service `%s' needs non existant service `%s'",
+ depinfo->service, service);
+ retval = -1;
+ }
+ 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 = rc_xmalloc (sizeof (rc_deptype_t));
+ dt = di->depends;
+ }
+ else
+ {
+ last_deptype->next = rc_xmalloc (sizeof (rc_deptype_t));
+ dt = last_deptype->next;
+ }
+ memset (dt, 0, sizeof (rc_deptype_t));
+ dt->type = strdup (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)
+ dt->services = 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")) == NULL)
+ eerror ("fopen `%s': %s", RC_DEPTREE, strerror (errno));
+ else
+ {
+ 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);
+ }
+
+ rc_free_deptree (deptree);
+
+ eend (retval, "Failed to update the service dependency tree");
+ return (retval);
+}
diff --git a/src/librc-misc.c b/src/librc-misc.c
new file mode 100644
index 0000000..604c551
--- /dev/null
+++ b/src/librc-misc.c
@@ -0,0 +1,750 @@
+/*
+ rc-misc.c
+ rc misc functions
+ Copyright 2007 Gentoo Foundation
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <limits.h>
+#include <regex.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "einfo.h"
+#include "rc-misc.h"
+#include "rc.h"
+#include "strlist.h"
+
+#define ERRX eerrorx("out of memory");
+
+#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_CONFIG "/etc/conf.d/rc"
+
+#define PATH_PREFIX RC_LIBDIR "bin:/bin:/sbin:/usr/bin:/usr/sbin"
+
+#ifndef S_IXUGO
+# define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH)
+#endif
+
+void *rc_xcalloc (size_t n, size_t size)
+{
+ void *value = calloc (n, size);
+
+ if (value)
+ return value;
+
+ ERRX
+}
+
+void *rc_xmalloc (size_t size)
+{
+ void *value = malloc (size);
+
+ if (value)
+ return (value);
+
+ ERRX
+}
+
+void *rc_xrealloc (void *ptr, size_t size)
+{
+ void *value = realloc (ptr, size);
+
+ if (value)
+ return (value);
+
+ ERRX
+}
+
+
+char *rc_xstrdup (const char *str)
+{
+ char *value;
+
+ if (! str)
+ return (NULL);
+
+ value = strdup (str);
+
+ if (value)
+ return (value);
+
+ ERRX
+}
+
+bool rc_is_env (const char *var, const char *val)
+{
+ char *v;
+
+ if (! var)
+ return (false);
+
+ v = getenv (var);
+ if (! v)
+ return (val == NULL ? true : false);
+
+ return (strcasecmp (v, val) == 0 ? true : false);
+}
+
+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) + 3;
+ i = 0;
+ va_start (ap, paths);
+ while ((p = va_arg (ap, char *)) != NULL)
+ length += strlen (p) + 1;
+ va_end (ap);
+
+ path = rc_xmalloc (length);
+ memset (path, 0, length);
+ memcpy (path, path1, strlen (path1));
+ pathp = path + strlen (path1) - 1;
+ if (*pathp != '/')
+ {
+ pathp++;
+ *pathp++ = '/';
+ }
+ else
+ pathp++;
+ memcpy (pathp, paths, strlen (paths));
+ pathp += strlen (paths);
+
+ va_start (ap, paths);
+ while ((p = va_arg (ap, char *)) != NULL)
+ {
+ if (*pathp != '/')
+ *pathp++ = '/';
+ i = strlen (p);
+ memcpy (pathp, p, i);
+ pathp += i;
+ }
+ va_end (ap);
+
+ *pathp++ = 0;
+
+ return (path);
+}
+
+bool rc_exists (const char *pathname)
+{
+ struct stat buf;
+
+ if (! pathname)
+ return (false);
+
+ if (stat (pathname, &buf) == 0)
+ return (true);
+
+ errno = 0;
+ return (false);
+}
+
+bool rc_is_file (const char *pathname)
+{
+ struct stat buf;
+
+ if (! pathname)
+ return (false);
+
+ if (stat (pathname, &buf) == 0)
+ return (S_ISREG (buf.st_mode));
+
+ errno = 0;
+ return (false);
+}
+
+bool rc_is_dir (const char *pathname)
+{
+ struct stat buf;
+
+ if (! pathname)
+ return (false);
+
+ if (stat (pathname, &buf) == 0)
+ return (S_ISDIR (buf.st_mode));
+
+ errno = 0;
+ return (false);
+}
+
+bool rc_is_link (const char *pathname)
+{
+ struct stat buf;
+
+ if (! pathname)
+ return (false);
+
+ if (lstat (pathname, &buf) == 0)
+ return (S_ISLNK (buf.st_mode));
+
+ errno = 0;
+ return (false);
+}
+
+bool rc_is_exec (const char *pathname)
+{
+ struct stat buf;
+
+ if (! pathname)
+ return (false);
+
+ if (lstat (pathname, &buf) == 0)
+ return (buf.st_mode & S_IXUGO);
+
+ errno = 0;
+ return (false);
+}
+
+char **rc_ls_dir (char **list, const char *dir, int options)
+{
+ DIR *dp;
+ struct dirent *d;
+
+ if (! dir)
+ return (list);
+
+ if ((dp = opendir (dir)) == NULL)
+ {
+ eerror ("failed to opendir `%s': %s", dir, strerror (errno));
+ return (list);
+ }
+
+ errno = 0;
+ while (((d = readdir (dp)) != NULL) && errno == 0)
+ {
+ if (d->d_name[0] != '.')
+ {
+ if (options & RC_LS_INITD)
+ {
+ int l = strlen (d->d_name);
+ char *init = rc_strcatpaths (RC_INITDIR, d->d_name, NULL);
+ bool ok = rc_exists (init);
+ free (init);
+ if (! ok)
+ 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;
+ }
+ list = rc_strlist_addsort (list, d->d_name);
+ }
+ }
+ closedir (dp);
+
+ if (errno != 0)
+ {
+ eerror ("failed to readdir `%s': %s", dir, strerror (errno));
+ rc_strlist_free (list);
+ return (NULL);
+ }
+
+ return (list);
+}
+
+bool rc_rm_dir (const char *pathname, bool top)
+{
+ DIR *dp;
+ struct dirent *d;
+
+ if (! pathname)
+ return (false);
+
+ if ((dp = opendir (pathname)) == NULL)
+ {
+ eerror ("failed to opendir `%s': %s", pathname, strerror (errno));
+ 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, NULL);
+ if (d->d_type == DT_DIR)
+ {
+ if (! rc_rm_dir (tmp, true))
+ {
+ free (tmp);
+ closedir (dp);
+ return (false);
+ }
+ }
+ else
+ {
+ if (unlink (tmp))
+ {
+ eerror ("failed to unlink `%s': %s", tmp, strerror (errno));
+ free (tmp);
+ closedir (dp);
+ return (false);
+ }
+ }
+ free (tmp);
+ }
+ }
+ if (errno != 0)
+ eerror ("failed to readdir `%s': %s", pathname, strerror (errno));
+ closedir (dp);
+
+ if (top && rmdir (pathname) != 0)
+ {
+ eerror ("failed to rmdir `%s': %s", pathname, strerror (errno));
+ return false;
+ }
+
+ return (true);
+}
+
+char **rc_get_config (char **list, const char *file)
+{
+ FILE *fp;
+ char buffer[RC_LINEBUFFER];
+ char *p;
+ char *token;
+ char *line;
+ char *linep;
+ char *linetok;
+ int i = 0;
+ bool replaced;
+ char *entry;
+ char *newline;
+
+ if (! (fp = fopen (file, "r")))
+ {
+ ewarn ("load_config_file `%s': %s", file, strerror (errno));
+ return (list);
+ }
+
+ 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 = rc_xstrdup (token);
+
+ 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 = rc_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 = rc_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)
+ {
+ list = rc_strlist_addsort (list, newline);
+ free (newline);
+ }
+ free (entry);
+ }
+ fclose (fp);
+
+ return (list);
+}
+
+char *rc_get_config_entry (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);
+}
+
+char **rc_get_list (char **list, const char *file)
+{
+ FILE *fp;
+ char buffer[RC_LINEBUFFER];
+ char *p;
+ char *token;
+
+ if (! (fp = fopen (file, "r")))
+ {
+ ewarn ("rc_get_list `%s': %s", file, strerror (errno));
+ return (list);
+ }
+
+ 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))
+ {
+ token[strlen (token) - 1] = 0;
+ list = rc_strlist_add (list, token);
+ }
+ }
+ fclose (fp);
+
+ return (list);
+}
+
+char **rc_filter_env (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 *p;
+ char *token;
+ char *sep;
+ char *e;
+ int pplen = strlen (PATH_PREFIX);
+
+ whitelist = rc_get_list (whitelist, SYS_WHITELIST);
+ if (! whitelist)
+ ewarn ("system environment whitelist (" SYS_WHITELIST ") missing");
+
+ whitelist = rc_get_list (whitelist, USR_WHITELIST);
+
+ if (! whitelist)
+ return (NULL);
+
+ if (rc_is_file (PROFILE_ENV))
+ profile = rc_get_config (profile, 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 = rc_xmalloc (sizeof (char *) * env_len);
+ snprintf (p, env_len, "export %s", env_name);
+ env_var = rc_get_config_entry (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 = rc_xmalloc (sizeof (char *) * env_len);
+ p += sprintf (e, "%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 = strdup (PATH_PREFIX);
+ char *npp = np;
+ char *tok = NULL;
+ while ((tok = strsep (&npp, ":")))
+ if (strcmp (tok, token) == 0)
+ break;
+ if (! tok)
+ p += sprintf (p, ":%s", token);
+ free (np);
+ }
+ *p++ = 0;
+ }
+ else
+ {
+ env_len = strlen (env_name) + strlen (env_var) + 2;
+ e = rc_xmalloc (sizeof (char *) * env_len);
+ snprintf (e, env_len, "%s=%s", env_name, env_var);
+ }
+
+ env = 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;
+ p = rc_xmalloc (sizeof (char *) * env_len);
+ snprintf (p, env_len, "PATH=%s", PATH_PREFIX);
+ env = rc_strlist_add (env, p);
+ free (p);
+ }
+
+ 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[RC_LINEBUFFER];
+ regex_t re;
+ bool retval = false;
+ int result;
+
+ if (! rc_exists (file))
+ return (false);
+
+ if (! (fp = fopen (file, "r")))
+ {
+ ewarn ("file_regex `%s': %s", file, strerror (errno));
+ return (false);
+ }
+
+ if ((result = regcomp (&re, regex, REG_EXTENDED | REG_NOSUB)) != 0)
+ {
+ fclose (fp);
+ regerror (result, &re, buffer, sizeof (buffer));
+ eerror ("file_regex: %s", buffer);
+ return (false);
+ }
+
+ while (fgets (buffer, RC_LINEBUFFER, fp))
+ {
+ if (regexec (&re, buffer, 0, NULL, 0) == 0)
+ {
+ retval = true;
+ break;
+ }
+ }
+ fclose (fp);
+ regfree (&re);
+
+ return (retval);
+}
+#endif
+
+char **rc_config_env (char **env)
+{
+ char *line;
+ int i;
+ char *p;
+ char **config = rc_get_config (NULL, RC_CONFIG);
+ char *e;
+ char sys[6];
+ struct utsname uts;
+ bool has_net_fs_list = false;
+ FILE *fp;
+ char buffer[PATH_MAX];
+
+ STRLIST_FOREACH (config, line, i)
+ {
+ p = strchr (line, '=');
+ if (! p)
+ continue;
+
+ *p = 0;
+ e = getenv (line);
+ if (! e)
+ {
+ *p = '=';
+ env = rc_strlist_add (env, line);
+ }
+ else
+ {
+ int len = strlen (line) + strlen (e) + 2;
+ char *new = rc_xmalloc (sizeof (char *) * len);
+ snprintf (new, len, "%s=%s", line, e);
+ env = rc_strlist_add (env, new);
+ free (new);
+ }
+ }
+ rc_strlist_free (config);
+
+ i = strlen ("RC_LIBDIR=//rcscripts") + strlen (LIBDIR) + 2;
+ line = rc_xmalloc (sizeof (char *) * i);
+ snprintf (line, i, "RC_LIBDIR=/" LIBDIR "/rcscripts");
+ env = rc_strlist_add (env, line);
+ free (line);
+
+ i += strlen ("/init.d");
+ line = rc_xmalloc (sizeof (char *) * i);
+ snprintf (line, i, "RC_SVCDIR=/" LIBDIR "/rcscripts/init.d");
+ env = rc_strlist_add (env, line);
+ free (line);
+
+ env = rc_strlist_add (env, "RC_BOOTLEVEL=" RC_LEVEL_BOOT);
+
+ p = rc_get_runlevel ();
+ i = strlen ("RC_SOFTLEVEL=") + strlen (p) + 1;
+ line = rc_xmalloc (sizeof (char *) * i);
+ snprintf (line, i, "RC_SOFTLEVEL=%s", p);
+ env = rc_strlist_add (env, line);
+ free (line);
+
+ if (rc_exists (RC_SVCDIR "ksoftlevel"))
+ {
+ if (! (fp = fopen (RC_SVCDIR "ksoftlevel", "r")))
+ eerror ("fopen `%s': %s", RC_SVCDIR "ksoftlevel",
+ strerror (errno));
+ else
+ {
+ 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 = rc_xmalloc (sizeof (char *) * i);
+ snprintf (line, i, "RC_DEFAULTLEVEL=%s", buffer);
+ env = rc_strlist_add (env, line);
+ free (line);
+ }
+ fclose (fp);
+ }
+ }
+ else
+ env = rc_strlist_add (env, "RC_DEFAULTLEVEL=" RC_LEVEL_DEFAULT);
+
+ memset (sys, 0, sizeof (sys));
+
+/* Linux can run some funky stuff like Xen, VServer, UML, etc
+ We store this special system in RC_SYS so our scripts run fast */
+#ifdef __linux__
+ if (rc_is_dir ("/proc/xen"))
+ {
+ fp = fopen ("/proc/xen/capabilities", "r");
+ if (fp)
+ {
+ fclose (fp);
+ if (file_regex ("/proc/xen/capabilities", "control_d"))
+ sprintf (sys, "XENU");
+ }
+ if (! sys)
+ sprintf (sys, "XEN0");
+ }
+ else if (file_regex ("/proc/cpuinfo", "UML"))
+ sprintf (sys, "UML");
+ else if (file_regex ("/proc/self/status",
+ "(s_context|VxID|envID):[[:space:]]*[1-9]"))
+ sprintf(sys, "VPS");
+#endif
+
+ /* Only add a NET_FS list if not defined */
+ STRLIST_FOREACH (env, line, i)
+ if (strncmp (line, "RC_NET_FS_LIST=", strlen ("RC_NET_FS_LIST=")) == 0)
+ {
+ has_net_fs_list = true;
+ break;
+ }
+ if (! has_net_fs_list)
+ {
+ i = strlen ("RC_NET_FS_LIST=") + strlen (RC_NET_FS_LIST_DEFAULT) + 1;
+ line = rc_xmalloc (sizeof (char *) * i);
+ snprintf (line, i, "RC_NET_FS_LIST=%s", RC_NET_FS_LIST_DEFAULT);
+ env = rc_strlist_add (env, line);
+ free (line);
+ }
+
+ if (sys[0])
+ {
+ i = strlen ("RC_SYS=") + strlen (sys) + 2;
+ line = rc_xmalloc (sizeof (char *) * i);
+ snprintf (line, i, "RC_SYS=%s", sys);
+ env = rc_strlist_add (env, line);
+ free (line);
+ }
+
+ /* 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 = rc_xmalloc (sizeof (char *) * i);
+ snprintf (line, i, "RC_UNAME=%s", uts.sysname);
+ env = rc_strlist_add (env, line);
+ free (line);
+ }
+
+ /* Set this var to ensure that things are POSIX, which makes scripts work
+ on non GNU systems with less effort. */
+ env = rc_strlist_add (env, "POSIXLY_CORRECT=1");
+
+ return (env);
+}
diff --git a/src/librc-strlist.c b/src/librc-strlist.c
new file mode 100644
index 0000000..981f654
--- /dev/null
+++ b/src/librc-strlist.c
@@ -0,0 +1,141 @@
+/*
+ librc-strlist.h
+ String list functions for using char ** arrays
+
+ Copyright 2007 Gentoo Foundation
+ Based on a previous implementation by Martin Schlemmer
+ Released under the GPLv2
+ */
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "rc.h"
+#include "rc-misc.h"
+
+char **rc_strlist_add (char **list, const char *item)
+{
+ char **newlist;
+ int i = 0;
+
+ if (! item)
+ return (list);
+
+ while (list && list[i])
+ i++;
+
+ newlist = rc_xrealloc (list, sizeof (char *) * (i + 2));
+ newlist[i] = rc_xstrdup (item);
+ newlist[i + 1] = NULL;
+
+ return (newlist);
+}
+
+static char **_rc_strlist_addsort (char **list, const char *item,
+ int (*sortfunc) (const char *s1,
+ const char *s2))
+{
+ char **newlist;
+ int i = 0;
+ char *tmp1;
+ char *tmp2;
+
+ if (! item)
+ return (list);
+
+ while (list && list[i])
+ i++;
+
+ newlist = rc_xrealloc (list, sizeof (char *) * (i + 2));
+
+ if (i == 0)
+ newlist[i] = NULL;
+ newlist[i + 1] = NULL;
+
+ i = 0;
+ while (newlist[i] && sortfunc (newlist[i], item) < 0)
+ i++;
+
+ tmp1 = newlist[i];
+ newlist[i] = rc_xstrdup (item);
+ do
+ {
+ i++;
+ tmp2 = newlist[i];
+ newlist[i] = tmp1;
+ tmp1 = tmp2;
+ } while (tmp1);
+
+ return (newlist);
+}
+
+char **rc_strlist_addsort (char **list, const char *item)
+{
+ return (_rc_strlist_addsort (list, item, strcoll));
+}
+
+char **rc_strlist_addsortc (char **list, const char *item)
+{
+ return (_rc_strlist_addsort (list, item, strcmp));
+}
+
+char **rc_strlist_delete (char **list, const char *item)
+{
+ int i = 0;
+
+ if (!list || ! item)
+ return (list);
+
+ while (list[i])
+ if (strcmp (list[i], item) == 0)
+ {
+ free (list[i]);
+ do
+ {
+ list[i] = list[i + 1];
+ i++;
+ } while (list[i]);
+ }
+
+ return (list);
+}
+
+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--;
+ }
+}
+
+void rc_strlist_free (char **list)
+{
+ int i = 0;
+
+ if (! list)
+ return;
+
+ while (list[i])
+ {
+ free (list[i]);
+ list[i++] = NULL;
+ }
+
+ free (list);
+}
diff --git a/src/librc.c b/src/librc.c
new file mode 100644
index 0000000..d9c4a53
--- /dev/null
+++ b/src/librc.c
@@ -0,0 +1,773 @@
+/*
+ librc
+ core RC functions
+ Copyright 2007 Gentoo Foundation
+ Released under the GPLv2
+ */
+
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <errno.h>
+#ifndef __linux__
+/* Although linux should work fine, gcc likes to bitch with our default
+ CFLAGS so we just don't include the file and use the GNU one defined
+ in string.h */
+#include <libgen.h>
+#endif
+#include <limits.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "strlist.h"
+
+/* usecs to wait while we poll the fifo */
+#define WAIT_INTERVAL 20000
+
+/* max secs to wait until a service comes up */
+#define WAIT_MAX 60
+
+#define SOFTLEVEL RC_SVCDIR "softlevel"
+
+static const char *rc_service_state_names[] = {
+ "started",
+ "stopped",
+ "starting",
+ "stopping",
+ "inactive",
+ "wasinactive",
+ "coldplugged",
+ "failed",
+ NULL
+};
+
+bool rc_runlevel_starting (void)
+{
+ return (rc_is_dir (RC_SVCDIR "softscripts.old"));
+}
+
+bool rc_runlevel_stopping (void)
+{
+ return (rc_is_dir (RC_SVCDIR "softscripts.new"));
+}
+
+char **rc_get_runlevels (void)
+{
+ char **dirs = rc_ls_dir (NULL, RC_RUNLEVELDIR, 0);
+ char **runlevels = NULL;
+ int i;
+ char *dir;
+
+ STRLIST_FOREACH (dirs, dir, i)
+ {
+ char *path = rc_strcatpaths (RC_RUNLEVELDIR, dir, NULL);
+ if (rc_is_dir (path))
+ runlevels = rc_strlist_addsort (runlevels, dir);
+ free (path);
+ }
+ rc_strlist_free (dirs);
+
+ return (runlevels);
+}
+
+char *rc_get_runlevel (void)
+{
+ FILE *fp;
+ static char buffer [PATH_MAX];
+
+ if (! (fp = fopen (SOFTLEVEL, "r")))
+ {
+ strcpy (buffer, "sysinit");
+ return (buffer);
+ }
+
+ if (fgets (buffer, PATH_MAX, fp))
+ {
+ int i = strlen (buffer) - 1;
+ if (buffer[i] == '\n')
+ buffer[i] = 0;
+ fclose (fp);
+ return (buffer);
+ }
+
+ fclose (fp);
+ strcpy (buffer, "sysinit");
+ return (buffer);
+}
+
+void rc_set_runlevel (const char *runlevel)
+{
+ FILE *fp = fopen (SOFTLEVEL, "w");
+ if (! fp)
+ eerrorx ("failed to open `" SOFTLEVEL "': %s", strerror (errno));
+ fprintf (fp, "%s", runlevel);
+ fclose (fp);
+}
+
+bool rc_runlevel_exists (const char *runlevel)
+{
+ char *path;
+ bool retval;
+
+ if (! runlevel)
+ return (false);
+
+ path = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, NULL);
+ retval = rc_is_dir (path);
+ free (path);
+ return (retval);
+}
+
+/* Resolve a service name to it's full path */
+char *rc_resolve_service (const char *service)
+{
+ char buffer[PATH_MAX];
+ char *file;
+ int r = 0;
+
+ if (! service)
+ return (NULL);
+
+ if (service[0] == '/')
+ return (strdup (service));
+
+ file = rc_strcatpaths (RC_SVCDIR, "started", service, NULL);
+ if (! rc_is_link (file))
+ {
+ free (file);
+ file = rc_strcatpaths (RC_SVCDIR, "inactive", service, NULL);
+ if (! rc_is_link (file))
+ {
+ free (file);
+ file = NULL;
+ }
+ }
+
+ memset (buffer, 0, sizeof (buffer));
+ if (file)
+ {
+ r = readlink (file, buffer, sizeof (buffer));
+ free (file);
+ if (r > 0)
+ return strdup (buffer);
+ }
+
+ snprintf (buffer, sizeof (buffer), RC_INITDIR "%s", service);
+ return (strdup (buffer));
+}
+
+bool rc_service_exists (const char *service)
+{
+ char *file;
+ bool retval = false;
+ int len;
+
+ 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_resolve_service (service);
+ if (rc_exists (file))
+ retval = rc_is_exec (file);
+ free (file);
+ return (retval);
+}
+
+bool rc_service_in_runlevel (const char *service, const char *runlevel)
+{
+ char *file;
+ bool retval;
+
+ if (! runlevel || ! service)
+ return (false);
+
+ if (! rc_service_exists (service))
+ return (false);
+
+ file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename (service), NULL);
+ retval = rc_exists (file);
+ free (file);
+
+ return (retval);
+}
+
+bool rc_mark_service (const char *service, const rc_service_state_t state)
+{
+ char *file;
+ int i = 0;
+ int skip_state = -1;
+ char *base;
+ char *init = rc_resolve_service (service);
+ bool skip_wasinactive = false;
+
+ if (! service)
+ return (false);
+
+ base = basename (service);
+
+ if (state != rc_service_stopped)
+ {
+ if (! rc_is_file(init))
+ {
+ free (init);
+ return (false);
+ }
+
+ file = rc_strcatpaths (RC_SVCDIR, rc_service_state_names[state], base, NULL);
+ if (rc_exists (file))
+ unlink (file);
+ i = symlink (init, file);
+ if (i != 0)
+ {
+ free (file);
+ free (init);
+ einfo ("%d %s %s", state, rc_service_state_names[state], base);
+ eerror ("symlink `%s' to `%s': %s", init, file, strerror (errno));
+ return (false);
+ }
+
+ free (file);
+ skip_state = state;
+ }
+
+ if (state == rc_service_coldplugged)
+ {
+ free (init);
+ return (true);
+ }
+
+ /* Remove any old states now */
+ i = 0;
+ while (rc_service_state_names[i])
+ {
+ if ((i != skip_state &&
+ i != rc_service_stopped &&
+ i != rc_service_coldplugged &&
+ i != rc_service_crashed) &&
+ (! skip_wasinactive || i != rc_service_wasinactive))
+ {
+ file = rc_strcatpaths (RC_SVCDIR, rc_service_state_names[i], base, NULL);
+ if (rc_exists (file))
+ {
+ if ((state == rc_service_starting ||
+ state == rc_service_stopping) &&
+ i == rc_service_inactive)
+ {
+ char *wasfile = rc_strcatpaths (RC_SVCDIR,
+ rc_service_state_names[rc_service_wasinactive],
+ base, NULL);
+
+ if (symlink (init, wasfile) != 0)
+ eerror ("symlink `%s' to `%s': %s", init, wasfile,
+ strerror (errno));
+
+ skip_wasinactive = true;
+ free (wasfile);
+ }
+
+ errno = 0;
+ if (unlink (file) != 0 && errno != ENOENT)
+ eerror ("failed to delete `%s': %s", file,
+ strerror (errno));
+ }
+ free (file);
+ }
+ i++;
+ }
+
+ /* 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, NULL);
+ if (rc_exists (file))
+ if (unlink (file) != 0)
+ eerror ("unlink `%s': %s", file, strerror (errno));
+ 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, NULL);
+
+ if (rc_is_dir (dir))
+ rc_rm_dir (dir, true);
+ free (dir);
+
+ dir = rc_strcatpaths (RC_SVCDIR, "daemons", base, NULL);
+ if (rc_is_dir (dir))
+ rc_rm_dir (dir, true);
+ free (dir);
+
+ rc_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", NULL);
+ char **dirs = rc_ls_dir (NULL, sdir, 0);
+ char *dir;
+ int serrno;
+
+ STRLIST_FOREACH (dirs, dir, i)
+ {
+ char *bdir = rc_strcatpaths (sdir, dir, NULL);
+ file = rc_strcatpaths (bdir, base, NULL);
+ if (rc_exists (file))
+ if (unlink (file) != 0)
+ eerror ("unlink `%s': %s", file, strerror (errno));
+ 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);
+}
+
+bool rc_service_state (const char *service, const rc_service_state_t state)
+{
+ char *file;
+ bool retval;
+
+ /* If the init script does not exist then we are stopped */
+ if (! rc_service_exists (service))
+ return (state == rc_service_stopped ? true : false);
+
+ /* We check stopped state by not being in any of the others */
+ if (state == rc_service_stopped)
+ return ( ! (rc_service_state (service, rc_service_started) ||
+ rc_service_state (service, rc_service_starting) ||
+ rc_service_state (service, rc_service_stopping) ||
+ rc_service_state (service, rc_service_inactive)));
+
+ /* The crashed state and scheduled states are virtual */
+ if (state == rc_service_crashed)
+ return (rc_service_daemons_crashed (service));
+ else if (state == rc_service_scheduled)
+ {
+ char **services = rc_services_scheduled_by (service);
+ retval = (services);
+ if (services)
+ free (services);
+ return (retval);
+ }
+
+ /* Now we just check if a file by the service name rc_exists
+ in the state dir */
+ file = rc_strcatpaths (RC_SVCDIR, rc_service_state_names[state],
+ basename (service), NULL);
+ retval = rc_exists (file);
+ free (file);
+ return (retval);
+}
+
+bool rc_get_service_option (const char *service, const char *option,
+ char *value)
+{
+ FILE *fp;
+ char buffer[1024];
+ char *file = rc_strcatpaths (RC_SVCDIR, "options", service, option, NULL);
+ bool retval = false;
+
+ if (rc_exists (file))
+ {
+ if ((fp = fopen (file, "r")) == NULL)
+ eerror ("fopen `%s': %s", file, strerror (errno));
+ else
+ {
+ memset (buffer, 0, sizeof (buffer));
+ while (fgets (buffer, RC_LINEBUFFER, fp))
+ {
+ memcpy (value, buffer, strlen (buffer));
+ value += strlen (buffer);
+ }
+ fclose (fp);
+ retval = true;
+ }
+ }
+
+ free (file);
+ return (retval);
+}
+
+bool rc_set_service_option (const char *service, const char *option,
+ const char *value)
+{
+ FILE *fp;
+ char *path = rc_strcatpaths (RC_SVCDIR, "options", service, NULL);
+ char *file = rc_strcatpaths (path, option, NULL);
+ bool retval = false;
+
+ if (! rc_is_dir (path))
+ {
+ if (mkdir (path, 0755) != 0)
+ {
+ eerror ("mkdir `%s': %s", path, strerror (errno));
+ free (path);
+ free (file);
+ return (false);
+ }
+ }
+
+ if ((fp = fopen (file, "w")) == NULL)
+ eerror ("fopen `%s': %s", file, strerror (errno));
+ else
+ {
+ if (value)
+ fprintf (fp, "%s", value);
+ fclose (fp);
+ retval = true;
+ }
+
+ free (path);
+ free (file);
+ return (retval);
+}
+
+static pid_t _exec_service (const char *service, const char *arg)
+{
+ char *file;
+ char *fifo;
+ pid_t pid = -1;
+ pid_t savedpid;
+ int status;
+
+ file = rc_resolve_service (service);
+ if (! rc_is_file (file))
+ {
+ rc_mark_service (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 (service), NULL);
+
+ if (mkfifo (fifo, 0600) != 0 && errno != EEXIST)
+ {
+ eerror ("unable to create fifo `%s': %s", fifo, strerror (errno));
+ free (fifo);
+ free (file);
+ return (-1);
+ }
+
+ if ((pid = fork ()) == 0)
+ {
+ char *myarg = strdup (arg);
+ int e = 0;
+ execl (file, file, myarg, NULL);
+ e = errno;
+ free (myarg);
+ unlink (fifo);
+ free (fifo);
+ eerrorx ("unable to exec `%s': %s", file, strerror (errno));
+ }
+
+ free (fifo);
+ free (file);
+
+ if (pid == -1)
+ {
+ eerror ("unable to fork: %s", strerror (errno));
+ return (pid);
+ }
+
+ if (rc_is_env ("RC_PARALLEL_STARTUP", "yes"))
+ return (pid);
+
+ savedpid = pid;
+ errno = 0;
+ do
+ {
+ pid = waitpid (savedpid, &status, 0);
+ if (pid < 0)
+ {
+ if (errno != ECHILD)
+ eerror ("waitpid %d: %s", savedpid, strerror (errno));
+ return (-1);
+ }
+ } while (! WIFEXITED (status) && ! WIFSIGNALED (status));
+
+ return (0);
+}
+
+pid_t rc_stop_service (const char *service)
+{
+ if (rc_service_state (service, rc_service_stopped))
+ return (0);
+
+ return (_exec_service (service, "stop"));
+}
+
+
+pid_t rc_start_service (const char *service)
+{
+ if (! rc_service_state (service, rc_service_stopped))
+ return (0);
+
+ return (_exec_service (service, "start"));
+}
+
+void rc_schedule_start_service (const char *service,
+ const char *service_to_start)
+{
+ char *dir;
+ char *init;
+ char *file;
+
+ if (! rc_service_exists (service) || ! rc_service_exists (service_to_start))
+ return;
+
+ dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename (service), NULL);
+ if (! rc_is_dir (dir))
+ if (mkdir (dir, 0755) != 0)
+ {
+ eerror ("mkdir `%s': %s", dir, strerror (errno));
+ free (dir);
+ return;
+ }
+
+ init = rc_resolve_service (service_to_start);
+ file = rc_strcatpaths (dir, basename (service_to_start), NULL);
+ if (! rc_exists (file) && symlink (init, file) != 0)
+ eerror ("symlink `%s' to `%s': %s", init, file, strerror (errno));
+
+ free (init);
+ free (file);
+ free (dir);
+}
+
+void rc_schedule_clear (const char *service)
+{
+ char *dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename (service), NULL);
+
+ if (rc_is_dir (dir))
+ rc_rm_dir (dir, true);
+ free (dir);
+}
+
+bool rc_wait_service (const char *service)
+{
+ char *fifo = rc_strcatpaths (RC_SVCDIR, "exclusive", basename (service), NULL);
+ struct timeval tv;
+ struct timeval stopat;
+ struct timeval now;
+ bool retval = false;
+
+ if (gettimeofday (&stopat, NULL) != 0)
+ {
+ eerror ("gettimeofday: %s", strerror (errno));
+ return (false);
+ }
+ stopat.tv_sec += WAIT_MAX;
+
+ while (true)
+ {
+ if (! rc_exists (fifo))
+ {
+ retval = true;
+ break;
+ }
+
+ tv.tv_sec = 0;
+ tv.tv_usec = WAIT_INTERVAL;
+ if (select (0, 0, 0, 0, &tv) < 0)
+ {
+ if (errno != EINTR)
+ eerror ("select: %s",strerror (errno));
+ break;
+ }
+
+ /* Don't hang around forever */
+ if (gettimeofday (&now, NULL) != 0)
+ {
+ eerror ("gettimeofday: %s", strerror (errno));
+ break;
+ }
+ if (timercmp (&now, &stopat, >))
+ break;
+ }
+
+ free (fifo);
+ return (retval);
+}
+
+char **rc_services_in_runlevel (const char *runlevel)
+{
+ char *dir;
+ char **list = NULL;
+
+ if (! runlevel)
+ return (rc_ls_dir (NULL, RC_INITDIR, RC_LS_INITD));
+
+ /* 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, NULL);
+ if (! rc_is_dir (dir))
+ eerror ("runlevel `%s' does not exist", runlevel);
+ else
+ list = rc_ls_dir (list, dir, RC_LS_INITD);
+
+ free (dir);
+ return (list);
+}
+
+char **rc_services_in_state (rc_service_state_t state)
+{
+ char *dir = rc_strcatpaths (RC_SVCDIR, rc_service_state_names[state], NULL);
+ char **list = NULL;
+
+ if (rc_is_dir (dir))
+ list = rc_ls_dir (list, dir, RC_LS_INITD);
+
+ free (dir);
+ return (list);
+}
+
+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_resolve_service (service);
+ file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename (service), NULL);
+ retval = (symlink (init, file) == 0);
+ free (init);
+ free (file);
+ return (retval);
+}
+
+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 (service), NULL);
+ if (unlink (file) == 0)
+ retval = true;
+
+ free (file);
+ return (retval);
+}
+
+char **rc_services_scheduled_by (const char *service)
+{
+ char **dirs = rc_ls_dir (NULL, 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, NULL);
+ if (rc_exists (file))
+ list = rc_strlist_add (list, file);
+ free (file);
+ }
+ rc_strlist_free (dirs);
+
+ return (list);
+}
+
+char **rc_services_scheduled (const char *service)
+{
+ char *dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename (service), NULL);
+ char **list = NULL;
+
+ if (rc_is_dir (dir))
+ list = rc_ls_dir (list, dir, RC_LS_INITD);
+
+ free (dir);
+ return (list);
+}
+
+bool rc_allow_plug (char *service)
+{
+ char *list;
+ char *p;
+ char *star;
+ char *token;
+ bool allow = true;
+ char *match = getenv ("RC_PLUG_SERVICES");
+ if (! match)
+ return true;
+
+ list = strdup (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);
+}
diff --git a/src/mountinfo.c b/src/mountinfo.c
new file mode 100644
index 0000000..1fc8442
--- /dev/null
+++ b/src/mountinfo.c
@@ -0,0 +1,246 @@
+/*
+ mountinfo.c
+ Obtains information about mounted filesystems.
+
+ Copyright 2007 Gentoo Foundation
+ */
+
+#include <sys/types.h>
+#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+#include <sys/param.h>
+#include <sys/ucred.h>
+#include <sys/mount.h>
+#elif defined(__linux__)
+#include <limits.h>
+#endif
+
+#include <errno.h>
+#include <limits.h>
+#include <regex.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "strlist.h"
+
+#if defined(__FreeBSD__) || defined(__NetBSD__) || defined (__OpenBSD__)
+static char **find_mounts (regex_t *node_regex, regex_t *fstype_regex,
+ char **mounts, bool list_nodes, bool list_fstype)
+{
+ struct statfs *mnts;
+ int nmnts;
+ int i;
+ char **list = NULL;
+
+ if ((nmnts = getmntinfo (&mnts, MNT_NOWAIT)) == 0)
+ eerrorx ("getmntinfo: %s", strerror (errno));
+
+ for (i = 0; i < nmnts; i++)
+ {
+ if (node_regex &&
+ regexec (node_regex, mnts[i].f_mntfromname, 0, NULL, 0) != 0)
+ continue;
+ if (fstype_regex &&
+ regexec (fstype_regex, mnts[i].f_fstypename, 0, NULL, 0) != 0)
+ continue;
+
+ if (mounts)
+ {
+ bool found = false;
+ int j;
+ char *mnt;
+ STRLIST_FOREACH (mounts, mnt, j)
+ if (strcmp (mnt, mnts[i].f_mntonname) == 0)
+ {
+ found = true;
+ break;
+ }
+ if (! found)
+ continue;
+ }
+
+ list = rc_strlist_addsortc (list, list_nodes ?
+ mnts[i].f_mntfromname :
+ list_fstype ? mnts[i].f_fstypename :
+ mnts[i].f_mntonname);
+ }
+
+ return (list);
+}
+
+#elif defined (__linux__)
+static char **find_mounts (regex_t *node_regex, regex_t *fstype_regex,
+ char **mounts, bool list_nodes, bool list_fstype)
+{
+ FILE *fp;
+ char buffer[PATH_MAX * 3];
+ char *p;
+ char *from;
+ char *to;
+ char *fstype;
+ char **list = NULL;
+
+ if ((fp = fopen ("/proc/mounts", "r")) == NULL)
+ eerrorx ("getmntinfo: %s", strerror (errno));
+
+ while (fgets (buffer, sizeof (buffer), fp))
+ {
+ p = buffer;
+ from = strsep (&p, " ");
+ if (node_regex &&
+ regexec (node_regex, from, 0, NULL, 0) != 0)
+ continue;
+
+ to = strsep (&p, " ");
+ fstype = strsep (&p, " ");
+ /* Skip the really silly rootfs */
+ if (strcmp (fstype, "rootfs") == 0)
+ continue;
+ if (fstype_regex &&
+ regexec (fstype_regex, fstype, 0, NULL, 0) != 0)
+ continue;
+
+ if (mounts)
+ {
+ bool found = false;
+ int j;
+ char *mnt;
+ STRLIST_FOREACH (mounts, mnt, j)
+ if (strcmp (mnt, to) == 0)
+ {
+ found = true;
+ break;
+ }
+ if (! found)
+ continue;
+ }
+
+ list = rc_strlist_addsortc (list,
+ list_nodes ?
+ list_fstype ? fstype :
+ from : to);
+ }
+ fclose (fp);
+
+ return (list);
+}
+
+#else
+# error "Operating system not supported!"
+#endif
+
+int main (int argc, char **argv)
+{
+ int i;
+ regex_t *fstype_regex = NULL;
+ regex_t *node_regex = NULL;
+ regex_t *skip_regex = NULL;
+ char **nodes = NULL;
+ char *node;
+ int result;
+ char buffer[256];
+ bool list_nodes = false;
+ bool list_fstype = false;
+ bool reverse = false;
+ char **mounts = NULL;
+
+ for (i = 1; i < argc; i++)
+ {
+ if (strcmp (argv[i], "--fstype-regex") == 0 && (i + 1 < argc))
+ {
+ i++;
+ if (fstype_regex)
+ free (fstype_regex);
+ fstype_regex = rc_xmalloc (sizeof (regex_t));
+ if ((result = regcomp (fstype_regex, argv[i],
+ REG_EXTENDED | REG_NOSUB)) != 0)
+ {
+ regerror (result, fstype_regex, buffer, sizeof (buffer));
+ eerrorx ("%s: invalid regex `%s'", argv[0], buffer);
+ }
+ continue;
+ }
+
+ if (strcmp (argv[i], "--node-regex") == 0 && (i + 1 < argc))
+ {
+ i++;
+ if (node_regex)
+ free (node_regex);
+ node_regex = rc_xmalloc (sizeof (regex_t));
+ if ((result = regcomp (node_regex, argv[i],
+ REG_EXTENDED | REG_NOSUB)) != 0)
+ {
+ regerror (result, node_regex, buffer, sizeof (buffer));
+ eerrorx ("%s: invalid regex `%s'", argv[0], buffer);
+ }
+ continue;
+ }
+
+ if (strcmp (argv[i], "--skip-regex") == 0 && (i + 1 < argc))
+ {
+ i++;
+ if (skip_regex)
+ free (skip_regex);
+ skip_regex = rc_xmalloc (sizeof (regex_t));
+ if ((result = regcomp (skip_regex, argv[i],
+ REG_EXTENDED | REG_NOSUB)) != 0)
+ {
+ regerror (result, skip_regex, buffer, sizeof (buffer));
+ eerrorx ("%s: invalid regex `%s'", argv[0], buffer);
+ }
+ continue;
+ }
+
+ if (strcmp (argv[i], "--fstype") == 0)
+ {
+ list_fstype = true;
+ continue;
+ }
+
+ if (strcmp (argv[i], "--node") == 0)
+ {
+ list_nodes = true;
+ continue;
+ }
+ if (strcmp (argv[i], "--reverse") == 0)
+ {
+ reverse = true;
+ continue;
+ }
+
+ if (argv[i][0] != '/')
+ eerrorx ("%s: `%s' is not a mount point", argv[0], argv[i]);
+
+ mounts = rc_strlist_add (mounts, argv[i]);
+ }
+
+ nodes = find_mounts (node_regex, fstype_regex, mounts,
+ list_nodes, list_fstype);
+
+ if (node_regex)
+ regfree (node_regex);
+ if (fstype_regex)
+ regfree (fstype_regex);
+
+ if (reverse)
+ rc_strlist_reverse (nodes);
+
+ result = EXIT_FAILURE;
+ STRLIST_FOREACH (nodes, node, i)
+ {
+ if (skip_regex && regexec (skip_regex, node, 0, NULL, 0) == 0)
+ continue;
+ printf ("%s\n", node);
+ result = EXIT_SUCCESS;
+ }
+ rc_strlist_free (nodes);
+
+ if (skip_regex)
+ free (skip_regex);
+
+ exit (result);
+}
+
diff --git a/src/rc-depend.c b/src/rc-depend.c
new file mode 100644
index 0000000..9d0b3af
--- /dev/null
+++ b/src/rc-depend.c
@@ -0,0 +1,120 @@
+/*
+ rc-depend
+ rc service dependency and ordering
+ Copyright 2006-2007 Gentoo Foundation
+ Released under the GPLv2
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "strlist.h"
+
+int main (int argc, char **argv)
+{
+ char **types = NULL;
+ char **services = NULL;
+ char **depends = NULL;
+ rc_depinfo_t *deptree = NULL;
+ rc_depinfo_t *di;
+ char *service;
+ int options = RC_DEP_TRACE;
+ bool first = true;
+ int i;
+ bool update = false;
+ char *runlevel = getenv ("RC_SOFTLEVEL");
+
+ if (! runlevel)
+ runlevel = rc_get_runlevel ();
+
+ for (i = 1; i < argc; i++)
+ {
+ if (strcmp (argv[i], "--update") == 0)
+ {
+ if (! update)
+ {
+ rc_update_deptree (true);
+ update = true;
+ }
+ continue;
+ }
+
+ if (strcmp (argv[i], "--strict") == 0)
+ {
+ options |= RC_DEP_STRICT;
+ continue;
+ }
+
+ if (strcmp (argv[i], "--notrace") == 0)
+ {
+ options &= RC_DEP_TRACE;
+ continue;
+ }
+
+ if (argv[i][0] == '-')
+ {
+ argv[i]++;
+ types = rc_strlist_add (types, argv[i]);
+ }
+ else
+ {
+ if ((deptree = rc_load_deptree ()) == NULL)
+ eerrorx ("failed to load deptree");
+
+ di = rc_get_depinfo (deptree, argv[i]);
+ if (! di)
+ eerror ("no dependency info for service `%s'", argv[i]);
+ else
+ services = rc_strlist_add (services, argv[i]);
+ }
+ }
+
+ if (! services)
+ {
+ rc_strlist_free (types);
+ rc_free_deptree (deptree);
+ if (update)
+ return (EXIT_SUCCESS);
+ eerrorx ("no services specified");
+ }
+
+ /* If we don't have any types, then supply some defaults */
+ if (! types)
+ {
+ types = rc_strlist_add (NULL, "ineed");
+ rc_strlist_add (types, "iuse");
+ }
+
+ depends = rc_get_depends (deptree, types, 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_free_deptree (deptree);
+
+ return (EXIT_SUCCESS);
+}
diff --git a/src/rc-misc.h b/src/rc-misc.h
new file mode 100644
index 0000000..5a4aa55
--- /dev/null
+++ b/src/rc-misc.h
@@ -0,0 +1,34 @@
+/*
+ rc-misc.h
+ This is private to us and not for user consumption
+ Copyright 2007 Gentoo Foundation
+ */
+
+#ifndef __RC_MISC_H__
+#define __RC_MISC_H__
+
+#ifndef LIBDIR
+# define LIBDIR "lib"
+#endif
+
+#define RC_LIBDIR "/" LIBDIR "/rcscripts/"
+#define RC_SVCDIR RC_LIBDIR "init.d/"
+#define RC_DEPTREE RC_SVCDIR "deptree"
+#define RC_RUNLEVELDIR "/etc/runlevels/"
+#define RC_INITDIR "/etc/init.d/"
+#define RC_CONFDIR "/etc/conf.d/"
+
+#define RC_SVCDIR_STARTING RC_SVCDIR "starting/"
+#define RC_SVCDIR_INACTIVE RC_SVCDIR "inactive/"
+#define RC_SVCDIR_STARTED RC_SVCDIR "started/"
+#define RC_SVCDIR_COLDPLUGGED RC_SVCDIR "coldplugged/"
+
+#define RC_PLUGINDIR RC_LIBDIR "plugins/"
+
+/* Max buffer to read a line from a file */
+#define RC_LINEBUFFER 4096
+
+/* Good defaults just incase user has none set */
+#define RC_NET_FS_LIST_DEFAULT "afs cifs coda davfs fuse gfs ncpfs nfs nfs4 ocfs2 shfs smbfs"
+
+#endif
diff --git a/src/rc-plugin.c b/src/rc-plugin.c
new file mode 100644
index 0000000..c02b6a8
--- /dev/null
+++ b/src/rc-plugin.c
@@ -0,0 +1,119 @@
+/*
+ librc-plugin.c
+ Simple plugin handler
+ Copyright 2007 Gentoo Foundation
+ Released under the GPLv2
+ */
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "rc-plugin.h"
+#include "strlist.h"
+
+typedef struct plugin
+{
+ char *name;
+ void *handle;
+ int (*hook) (rc_hook_t hook, const char *name);
+ struct plugin *next;
+} plugin_t;
+
+static plugin_t *plugins = NULL;
+
+void rc_plugin_load (void)
+{
+ char **files;
+ char *file;
+ int i;
+ plugin_t *plugin = plugins;
+
+ /* Ensure some sanity here */
+ rc_plugin_unload ();
+
+ if (! rc_exists (RC_PLUGINDIR))
+ return;
+
+ files = rc_ls_dir (NULL, RC_PLUGINDIR, 0);
+ STRLIST_FOREACH (files, file, i)
+ {
+ char *p = rc_strcatpaths (RC_PLUGINDIR, file, NULL);
+ void *h = dlopen (p, RTLD_LAZY);
+ char *func;
+ void *f;
+
+ if (! h)
+ {
+ eerror ("dlopen `%s': %s", p, dlerror ());
+ free (p);
+ continue;
+ }
+
+ func = file;
+ file = strsep (&func, ".");
+ func = rc_xmalloc (strlen (file) + strlen ("__hook") + 1);
+ sprintf (func, "_%s_hook", file);
+
+ f = dlsym (h, func);
+ if (! f)
+ {
+ eerror ("`%s' does not expose the symbol `%s'", p, func);
+ dlclose (h);
+ }
+ else
+ {
+ if (plugin)
+ {
+ plugin->next = rc_xmalloc (sizeof (plugin_t));
+ plugin = plugin->next;
+ }
+ else
+ plugin = plugins = rc_xmalloc (sizeof (plugin_t));
+
+ memset (plugin, 0, sizeof (plugin_t));
+ plugin->name = strdup (file);
+ plugin->handle = h;
+ plugin->hook = f;
+ }
+
+ free (func);
+ free (p);
+ }
+
+ rc_strlist_free (files);
+}
+
+void rc_plugin_run (rc_hook_t hook, const char *value)
+{
+ plugin_t *plugin = plugins;
+
+ while (plugin)
+ {
+ if (plugin->hook)
+ plugin->hook (hook, value);
+
+ 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
new file mode 100644
index 0000000..b4391ad
--- /dev/null
+++ b/src/rc-plugin.h
@@ -0,0 +1,15 @@
+/*
+ librc-plugin.h
+ Private instructions to use plugins
+ Copyright 2007 Gentoo Foundation
+ Released under the GPLv2
+ */
+
+#ifndef __LIBRC_PLUGIN_H__
+#define __LIBRC_PLUGIN_H__
+
+void rc_plugin_load ();
+void rc_plugin_unload ();
+void rc_plugin_run (rc_hook_t, const char *value);
+
+#endif
diff --git a/src/rc-status.c b/src/rc-status.c
new file mode 100644
index 0000000..5f2f9b1
--- /dev/null
+++ b/src/rc-status.c
@@ -0,0 +1,142 @@
+/*
+ rc-status
+ Display the status of the services in runlevels
+ Copyright 2007 Gentoo Foundation
+ Released under the GPLv2
+ */
+
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "strlist.h"
+
+static void print_level (char *level)
+{
+ printf ("Runlevel: ");
+ PEINFO_HILITE;
+ printf ("%s\n", level);
+ PEINFO_NORMAL;
+}
+
+static void print_service (char *service)
+{
+ char status[10];
+ int cols = printf (" %s\n", service);
+ einfo_color_t color = einfo_bad;
+
+ if (rc_service_state (service, rc_service_stopping))
+ snprintf (status, sizeof (status), "stopping ");
+ else if (rc_service_state (service, rc_service_starting))
+ {
+ snprintf (status, sizeof (status), "starting ");
+ color = einfo_warn;
+ }
+ else if (rc_service_state (service, rc_service_inactive))
+ {
+ snprintf (status, sizeof (status), "inactive ");
+ color = einfo_warn;
+ }
+ else if (geteuid () == 0 && rc_service_state (service, rc_service_crashed))
+ snprintf (status, sizeof (status), " crashed ");
+ else if (rc_service_state (service, rc_service_started))
+ {
+ snprintf (status, sizeof (status), " started ");
+ color = einfo_good;
+ }
+ else if (rc_service_state (service, rc_service_scheduled))
+ {
+ snprintf (status, sizeof (status), "scheduled");
+ color = einfo_warn;
+ }
+ else
+ snprintf (status, sizeof (status), " stopped ");
+ ebracket (cols, color, status);
+}
+
+int main (int argc, char **argv)
+{
+ char **levels = NULL;
+ char **services = NULL;
+ char *level;
+ char *service;
+ char c;
+ int option_index = 0;
+ int i;
+ int j;
+
+ const struct option longopts[] =
+ {
+ {"all", no_argument, NULL, 'a'},
+ {"list", no_argument, NULL, 'l'},
+ {"servicelist", no_argument, NULL, 's'},
+ {"unused", no_argument, NULL, 'u'},
+ {NULL, 0, NULL, 0}
+ };
+
+ while ((c = getopt_long(argc, argv, "alsu", longopts, &option_index)) != -1)
+ switch (c)
+ {
+ case 'a':
+ levels = rc_get_runlevels ();
+ break;
+ case 'l':
+ levels = rc_get_runlevels ();
+ 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_get_runlevels ();
+ 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 '?':
+ exit (EXIT_FAILURE);
+ default:
+ exit (EXIT_FAILURE);
+ }
+
+ while (optind < argc)
+ levels = rc_strlist_add (levels, argv[optind++]);
+
+ if (! levels)
+ levels = rc_strlist_add (NULL, rc_get_runlevel ());
+
+ STRLIST_FOREACH (levels, level, i)
+ {
+ print_level (level);
+ services = rc_services_in_runlevel (level);
+ STRLIST_FOREACH (services, service, j)
+ print_service (service);
+ rc_strlist_free (services);
+ }
+
+ rc_strlist_free (levels);
+
+ return (EXIT_SUCCESS);
+}
diff --git a/src/rc-update.c b/src/rc-update.c
new file mode 100644
index 0000000..8d50a4a
--- /dev/null
+++ b/src/rc-update.c
@@ -0,0 +1,162 @@
+/*
+ rc-update
+ Manage init scripts and runlevels
+ Copyright 2007 Gentoo Foundation
+ Released under the GPLv2
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "strlist.h"
+
+static char *applet = NULL;
+
+static bool add (const char *runlevel, const char *service)
+{
+ bool retval = true;
+
+ if (! rc_runlevel_exists (runlevel))
+ {
+ ewarn ("runlevel `%s' does not exist", runlevel);
+ return (false);
+ }
+ if (rc_service_in_runlevel (service, runlevel))
+ {
+ ewarn ("%s already installed in runlevel `%s'; skipping",
+ service, runlevel);
+ return (false);
+ }
+
+ if (rc_service_add (runlevel, service))
+ einfo ("%s added to runlevel %s", service, runlevel);
+ else
+ {
+ eerror ("%s: failed to add service `%s' to runlevel `%s': %s",
+ applet, service, runlevel, strerror (errno));
+ retval = false;
+ }
+
+ return (retval);
+}
+
+int main (int argc, char **argv)
+{
+ int i;
+ int j;
+ char *service;
+ char **runlevels = NULL;
+ char *runlevel;
+
+ applet = argv[0];
+ if (argc < 2 ||
+ strcmp (argv[1], "show") == 0 ||
+ strcmp (argv[1], "-s") == 0)
+ {
+ bool verbose = false;
+ char **services = rc_services_in_runlevel (NULL);
+
+ for (i = 2; i < argc; i++)
+ {
+ if (strcmp (argv[i], "--verbose") == 0 ||
+ strcmp (argv[i], "-v") == 0)
+ verbose = true;
+ else
+ runlevels = rc_strlist_add (runlevels, argv[i]);
+ }
+
+ if (! runlevels)
+ runlevels = rc_get_runlevels ();
+
+ STRLIST_FOREACH (services, service, i)
+ {
+ char **in = NULL;
+ bool inone = false;
+
+ STRLIST_FOREACH (runlevels, runlevel, j)
+ {
+ if (rc_service_in_runlevel (service, runlevel))
+ {
+ in = rc_strlist_add (in, runlevel);
+ inone = true;
+ }
+ else
+ {
+ char buffer[PATH_MAX];
+ memset (buffer, ' ', strlen (runlevel));
+ buffer[strlen (runlevel)] = 0;
+ in = rc_strlist_add (in, buffer);
+ }
+ }
+
+ if (! inone && ! verbose)
+ continue;
+
+ printf (" %20s |", service);
+ STRLIST_FOREACH (in, runlevel, j)
+ printf (" %s", runlevel);
+ printf ("\n");
+ }
+
+ return (EXIT_SUCCESS);
+ }
+
+ if (geteuid () != 0)
+ eerrorx ("%s: must be root to add or delete services from runlevels",
+ applet);
+
+ if (! (service = argv[2]))
+ eerrorx ("%s: no service specified", applet);
+
+ if (strcmp (argv[1], "add") == 0 ||
+ strcmp (argv[1], "-a") == 0)
+ {
+ if (! service)
+ eerrorx ("%s: no service specified", applet);
+ if (! rc_service_exists (service))
+ eerrorx ("%s: service `%s' does not exist", applet, service);
+
+ if (argc < 4)
+ add (rc_get_runlevel (), service);
+
+ for (i = 3; i < argc; i++)
+ add (argv[i], service);
+
+ return (EXIT_SUCCESS);
+ }
+
+ if (strcmp (argv[1], "delete") == 0 ||
+ strcmp (argv[1], "del") == 0 ||
+ strcmp (argv[1], "-d") == 0)
+ {
+ for (i = 3; i < argc; i++)
+ runlevels = rc_strlist_add (runlevels, argv[i]);
+
+ if (! runlevels)
+ runlevels = rc_strlist_add (runlevels, rc_get_runlevel ());
+
+ STRLIST_FOREACH (runlevels, runlevel, i)
+ {
+ if (rc_service_in_runlevel (service, runlevel))
+ {
+ if (rc_service_delete (runlevel, service))
+ einfo ("%s removed from runlevel %s", service, runlevel);
+ else
+ eerror ("%s: failed to remove service `%s' from runlevel `%s': %s",
+ applet, service, runlevel, strerror (errno));
+ }
+ }
+
+ return (EXIT_SUCCESS);
+ }
+
+ eerrorx ("%s: unknown command `%s'", applet, argv[1]);
+}
diff --git a/src/rc.c b/src/rc.c
new file mode 100644
index 0000000..b9b4c82
--- /dev/null
+++ b/src/rc.c
@@ -0,0 +1,1174 @@
+/*
+ rc.c
+ rc - manager for init scripts which control the startup, shutdown
+ and the running of daemons on a Gentoo system.
+
+ Also a multicall binary for various commands that can be used in shell
+ scripts to query service state, mark service state and provide the
+ Gentoo einfo family of informational functions.
+
+ Copyright 2007 Gentoo Foundation
+ Released under the GPLv2
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <ctype.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "rc-plugin.h"
+#include "strlist.h"
+
+#define INITSH RC_LIBDIR "sh/init.sh"
+#define HALTSH RC_INITDIR "halt.sh"
+
+#define RC_SVCDIR_STARTING RC_SVCDIR "starting/"
+#define RC_SVCDIR_INACTIVE RC_SVCDIR "inactive/"
+#define RC_SVCDIR_STARTED RC_SVCDIR "started/"
+#define RC_SVCDIR_COLDPLUGGED RC_SVCDIR "coldplugged/"
+
+#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 **env = NULL;
+static char **newenv = NULL;
+static char **coldplugged_services;
+static char **stop_services = NULL;
+static char **start_services = NULL;
+static rc_depinfo_t *deptree = NULL;
+static char **types = NULL;
+static char *mycmd = NULL;
+static char *myarg = NULL;
+static char *tmp = NULL;
+static char *applet = NULL;
+
+struct termios *termios_orig;
+
+static void cleanup (void)
+{
+ rc_plugin_unload ();
+
+ if (termios_orig)
+ {
+ tcsetattr (STDIN_FILENO, TCSANOW, termios_orig);
+ free (termios_orig);
+ }
+
+ if (env)
+ rc_strlist_free (env);
+ if (newenv)
+ rc_strlist_free (newenv);
+ if (coldplugged_services)
+ rc_strlist_free (coldplugged_services);
+ if (stop_services)
+ rc_strlist_free (stop_services);
+ if (start_services)
+ rc_strlist_free (start_services);
+ if (deptree)
+ rc_free_deptree (deptree);
+ if (types)
+ rc_strlist_free (types);
+ if (mycmd)
+ free (mycmd);
+ if (myarg)
+ free (myarg);
+
+ /* Clean runlevel start, stop markers */
+ if (rc_is_dir (RC_SVCDIR "softscripts.new"))
+ rc_rm_dir (RC_SVCDIR "softscripts.new", true);
+ if (rc_is_dir (RC_SVCDIR "softscripts.old"))
+ rc_rm_dir (RC_SVCDIR "softscripts.old", true);
+}
+
+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;
+
+ if (strcmp (applet, "eend") == 0 ||
+ strcmp (applet, "ewend") == 0 ||
+ strcmp (applet, "veend") == 0 ||
+ strcmp (applet, "vweend") == 0)
+ {
+ if (argc > 0)
+ {
+ errno = 0;
+ retval = strtol (argv[0], NULL, 0);
+ if (errno != 0)
+ retval = EXIT_FAILURE;
+ else
+ {
+ argc--;
+ argv++;
+ }
+ }
+ else
+ retval = EXIT_FAILURE;
+ }
+
+ if (argc > 0)
+ {
+ for (i = 0; i < argc; i++)
+ l += strlen (argv[i]) + 1;
+
+ message = rc_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 = strdup ("%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, "veinfo") == 0)
+ veinfo (fmt, message);
+ else if (strcmp (applet, "veinfon") == 0)
+ veinfon (fmt, message);
+ else if (strcmp (applet, "vewarn") == 0)
+ vewarn (fmt, message);
+ else if (strcmp (applet, "vewarnn") == 0)
+ vewarnn (fmt, message);
+ else if (strcmp (applet, "vebegin") == 0)
+ vebegin (fmt, message);
+ else if (strcmp (applet, "veend") == 0)
+ veend (retval, fmt, message);
+ else if (strcmp (applet, "vewend") == 0)
+ vewend (retval, fmt, message);
+ else if (strcmp (applet, "eindent") == 0)
+ eindent ();
+ else if (strcmp (applet, "eoutdent") == 0)
+ eoutdent ();
+ else if (strcmp (applet, "veindent") == 0)
+ veindent ();
+ else if (strcmp (applet, "veoutdent") == 0)
+ veoutdent ();
+ else if (strcmp (applet, "eflush") == 0)
+ eflush ();
+ 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;
+
+ if (argc < 1 || ! argv[0] || strlen (argv[0]) == 0)
+ eerrorx ("%s: no service specified", applet);
+
+ if (strcmp (applet, "service_started") == 0)
+ ok = rc_service_state (argv[0], rc_service_started);
+ else if (strcmp (applet, "service_stopped") == 0)
+ ok = rc_service_state (argv[0], rc_service_stopped);
+ else if (strcmp (applet, "service_inactive") == 0)
+ ok = rc_service_state (argv[0], rc_service_inactive);
+ else if (strcmp (applet, "service_starting") == 0)
+ ok = rc_service_state (argv[0], rc_service_starting);
+ else if (strcmp (applet, "service_stopping") == 0)
+ ok = rc_service_state (argv[0], rc_service_stopping);
+ else if (strcmp (applet, "service_coldplugged") == 0)
+ ok = rc_service_state (argv[0], rc_service_coldplugged);
+ else if (strcmp (applet, "service_wasinactive") == 0)
+ ok = rc_service_state (argv[0], rc_service_wasinactive);
+ else if (strcmp (applet, "service_started_daemon") == 0)
+ {
+ int idx = 0;
+ if (argc > 2)
+ sscanf (argv[2], "%d", &idx);
+ exit (rc_service_started_daemon (argv[0], argv[1], 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");
+
+ if (argc < 1 || ! argv[0] || strlen (argv[0]) == 0)
+ eerrorx ("%s: no service specified", applet);
+
+ if (strcmp (applet, "mark_service_started") == 0)
+ ok = rc_mark_service (argv[0], rc_service_started);
+ else if (strcmp (applet, "mark_service_stopped") == 0)
+ ok = rc_mark_service (argv[0], rc_service_stopped);
+ else if (strcmp (applet, "mark_service_inactive") == 0)
+ ok = rc_mark_service (argv[0], rc_service_inactive);
+ else if (strcmp (applet, "mark_service_starting") == 0)
+ ok = rc_mark_service (argv[0], rc_service_starting);
+ else if (strcmp (applet, "mark_service_stopping") == 0)
+ ok = rc_mark_service (argv[0], rc_service_stopping);
+ else if (strcmp (applet, "mark_service_coldplugged") == 0)
+ ok = rc_mark_service (argv[0], rc_service_coldplugged);
+ 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, argv[0]) == 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 exclsive time test. This ensures that it's not
+ in control as well */
+ l = strlen (RC_SVCDIR "exclusive") +
+ strlen (svcname) +
+ strlen (runscript_pid) +
+ 4;
+ mtime = rc_xmalloc (l);
+ snprintf (mtime, l, RC_SVCDIR "exclusive/%s.%s",
+ svcname, runscript_pid);
+ if (rc_exists (mtime) && unlink (mtime) != 0)
+ eerror ("%s: unlink: %s", applet, strerror (errno));
+ free (mtime);
+ }
+
+ return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+static int do_options (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, "get_options") == 0)
+ {
+ char buffer[1024];
+ memset (buffer, 0, 1024);
+ ok = rc_get_service_option (service, argv[0], buffer);
+ if (ok)
+ printf ("%s", buffer);
+ }
+ else if (strcmp (applet, "save_options") == 0)
+ ok = rc_set_service_option (service, argv[0], argv[1]);
+ else
+ eerrorx ("%s: unknown applet", applet);
+
+ return (ok ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+static char read_key (bool block)
+{
+ struct termios termios;
+ char c = 0;
+
+ if (! isatty (STDIN_FILENO))
+ 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 = rc_xmalloc (sizeof (struct termios));
+ tcgetattr (STDIN_FILENO, termios_orig);
+ }
+
+ tcgetattr (STDIN_FILENO, &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 (STDIN_FILENO, TCSANOW, &termios);
+
+ read (STDIN_FILENO, &c, 1);
+
+ tcsetattr (STDIN_FILENO, TCSANOW, termios_orig);
+
+ return (c);
+}
+
+static bool want_interactive (void)
+{
+ char 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__
+ if (cont)
+ {
+ int status = 0;
+ pid_t pid = fork();
+
+ if (pid == -1)
+ eerrorx ("%s: fork: %s", applet, strerror (errno));
+ if (pid == 0)
+ {
+ newenv = rc_filter_env ();
+ mycmd = rc_xstrdup ("/sbin/sulogin");
+ myarg = rc_xstrdup (getenv ("CONSOLE"));
+ execle (mycmd, mycmd, myarg, NULL, newenv);
+ eerrorx ("%s: unable to exec `/sbin/sulogin': %s", applet, strerror (errno));
+ }
+ waitpid (pid, &status, 0);
+ }
+ else
+ {
+
+ newenv = rc_filter_env ();
+ mycmd = rc_xstrdup ("/sbin/sulogin");
+ myarg = rc_xstrdup (getenv ("CONSOLE"));
+ execle (mycmd, mycmd, myarg, NULL, newenv);
+ eerrorx ("%s: unable to exec `/sbin/sulogin': %s", applet, strerror (errno));
+ }
+#else
+ /* Appease gcc */
+ cont = cont;
+ exit (EXIT_SUCCESS);
+#endif
+}
+
+static void set_ksoftlevel (const char *runlevel)
+{
+ FILE *fp;
+
+ if (! runlevel ||
+ strcmp (runlevel, RC_LEVEL_BOOT) == 0 ||
+ strcmp (runlevel, RC_LEVEL_SINGLE) == 0 ||
+ strcmp (runlevel, RC_LEVEL_SYSINIT) == 0)
+ {
+ if (rc_exists (RC_SVCDIR "ksoftlevel") &&
+ unlink (RC_SVCDIR "ksoftlevel") != 0)
+ eerror ("unlink `%s': %s", RC_SVCDIR "ksoftlevel", strerror (errno));
+ return;
+ }
+
+ if (! (fp = fopen (RC_SVCDIR "ksoftlevel", "w")))
+ {
+ eerror ("fopen `%s': %s", RC_SVCDIR "ksoftlevel", strerror (errno));
+ return;
+ }
+
+ fprintf (fp, "%s", runlevel);
+ fclose (fp);
+}
+
+static void wait_for_services ()
+{
+ int status = 0;
+ struct timeval tv;
+ while (wait (&status) != -1);
+
+ /* Wait for a little bit to flush our ebuffer */
+ tv.tv_usec = 50000;
+ tv.tv_sec = 0;
+ select (0, NULL, NULL, NULL, &tv);
+}
+
+int main (int argc, char **argv)
+{
+ char *RUNLEVEL = NULL;
+ char *PREVLEVEL = NULL;
+ char *runlevel = NULL;
+ char *newlevel = NULL;
+ char *service = NULL;
+ char **deporder = NULL;
+ 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];
+
+ if (argv[0])
+ applet = basename (argv[0]);
+
+ if (! applet)
+ eerrorx ("arguments required");
+
+ argc--;
+ argv++;
+
+ /* Handle multicall stuff */
+ if (applet[0] == 'e' || (applet[0] == 'v' && applet[1] == 'e'))
+ exit (do_e (argc, argv));
+
+ if (strncmp (applet, "service_", strlen ("service_")) == 0)
+ exit (do_service (argc, argv));
+
+ if (strcmp (applet, "get_options") == 0 ||
+ strcmp (applet, "save_options") == 0)
+ exit (do_options (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);
+ else if (strcmp (applet, "color_terminal") == 0)
+ exit (colour_terminal () ? 0 : 1);
+
+ if (strcmp (applet, "rc" ) != 0)
+ eerrorx ("%s: unknown applet", applet);
+
+ /* 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);
+
+ atexit (cleanup);
+ newlevel = argv[0];
+
+ /* Ensure our environment is pure
+ Also, add our configuration to it */
+ env = rc_filter_env ();
+ env = rc_config_env (env);
+
+ 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 = rc_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 */
+ }
+
+ /* Enable logging */
+ setenv ("RC_ELOG", "rc", 1);
+
+ interactive = rc_exists (INTERACTIVE);
+ rc_plugin_load ();
+
+ /* 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");
+
+ if (RUNLEVEL && newlevel)
+ {
+ if (strcmp (RUNLEVEL, "S") == 0 || strcmp (RUNLEVEL, "1") == 0)
+ {
+ /* OK, we're either in runlevel 1 or single user mode */
+ if (strcmp (newlevel, RC_LEVEL_SYSINIT) == 0)
+ {
+ struct utsname uts;
+ pid_t pid;
+ pid_t wpid;
+ int status = 0;
+#ifdef __linux__
+ FILE *fp;
+#endif
+
+ uname (&uts);
+
+ printf ("\n");
+ PEINFO_GOOD;
+ printf (" Gentoo/%s; ", uts.sysname);
+ PEINFO_BRACKET;
+ printf ("http://www.gentoo.org/");
+ PEINFO_NORMAL;
+ printf ("\n Copyright 1999-2007 Gentoo Foundation; "
+ "Distributed under the GPLv2\n\n");
+
+ printf ("Press ");
+ PEINFO_GOOD;
+ printf ("I");
+ PEINFO_NORMAL;
+ printf (" to enter interactive boot mode\n\n");
+
+ setenv ("RC_SOFTLEVEL", newlevel, 1);
+ rc_plugin_run (rc_hook_runlevel_start_in, newlevel);
+
+ if ((pid = fork ()) == -1)
+ eerrorx ("%s: fork: %s", applet, strerror (errno));
+
+ if (pid == 0)
+ {
+ mycmd = rc_xstrdup (INITSH);
+ execl (mycmd, mycmd, NULL);
+ eerrorx ("%s: unable to exec `" INITSH "': %s",
+ applet, strerror (errno));
+ }
+
+ do
+ {
+ wpid = waitpid (pid, &status, 0);
+ if (wpid < 1)
+ eerror ("waitpid: %s", strerror (errno));
+ } while (! WIFEXITED (status) && ! WIFSIGNALED (status));
+
+ if (! WIFEXITED (status) || ! WEXITSTATUS (status) == 0)
+ exit (EXIT_FAILURE);
+
+ /* If we requested a softlevel, save it now */
+#ifdef __linux__
+ set_ksoftlevel (NULL);
+
+ if ((fp = fopen ("/proc/cmdline", "r")))
+ {
+ char buffer[RC_LINEBUFFER];
+ char *soft;
+
+ memset (buffer, 0, sizeof (buffer));
+ if (fgets (buffer, RC_LINEBUFFER, fp) &&
+ (soft = strstr (buffer, "softlevel=")))
+ {
+ i = soft - buffer;
+ if (i == 0 || buffer[i - 1] == ' ')
+ {
+ char *level;
+
+ /* Trim the trailing carriage return if present */
+ i = strlen (buffer) - 1;
+ if (buffer[i] == '\n')
+ buffer[i] = 0;
+
+ soft += strlen ("softlevel=");
+ level = strsep (&soft, " ");
+ set_ksoftlevel (level);
+ }
+ }
+ fclose (fp);
+ }
+#endif
+ rc_plugin_run (rc_hook_runlevel_start_out, newlevel);
+
+ if (want_interactive ())
+ mark_interactive ();
+
+ exit (EXIT_SUCCESS);
+ }
+
+#ifdef __linux__
+ /* Parse the inittab file so we can work out the level to telinit */
+ if (strcmp (newlevel, RC_LEVEL_BOOT) != 0 &&
+ strcmp (newlevel, RC_LEVEL_SINGLE) != 0)
+ {
+ char **inittab = rc_get_list (NULL, "/etc/inittab");
+ char *line;
+ char *p;
+ char *token;
+ char lvl[2] = {0, 0};
+
+ STRLIST_FOREACH (inittab, line, i)
+ {
+ p = line;
+ token = strsep (&p, ":");
+ if (! token || token[0] != 'l')
+ continue;
+
+ if ((token = strsep (&p, ":")) == NULL)
+ continue;
+
+ /* Snag the level */
+ lvl[0] = token[0];
+
+ /* The name is spaced after this */
+ if ((token = strsep (&p, " ")) == NULL)
+ continue;
+
+ if ((token = strsep (&p, " ")) == NULL)
+ continue;
+
+ if (strcmp (token, newlevel) == 0)
+ break;
+ }
+ rc_strlist_free (inittab);
+
+ /* We have a level, so telinit into it */
+ if (lvl[0] == 0)
+ {
+ eerrorx ("%s: couldn't find a runlevel called `%s'",
+ applet, newlevel);
+ }
+ else
+ {
+ mycmd = rc_xstrdup ("/sbin/telinit");
+ myarg = rc_xstrdup (lvl);
+ execl (mycmd, mycmd, myarg, NULL);
+ eerrorx ("%s: unable to exec `/sbin/telinit': %s",
+ applet, strerror (errno));
+ }
+ }
+#endif
+ }
+ }
+
+ /* Check we're in the runlevel requested, ie from
+ rc single
+ rc shutdown
+ rc reboot
+ */
+ if (newlevel)
+ {
+ if (myarg)
+ {
+ free (myarg);
+ myarg = NULL;
+ }
+
+ 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);
+#ifdef __linux__
+ mycmd = rc_xstrdup ("/sbin/telinit");
+ myarg = rc_xstrdup ("S");
+ execl (mycmd, mycmd, myarg, NULL);
+ eerrorx ("%s: unable to exec `/%s': %s",
+ mycmd, 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
+ }
+ }
+ else if (strcmp (newlevel, RC_LEVEL_REBOOT) == 0)
+ {
+ if (! RUNLEVEL ||
+ strcmp (RUNLEVEL, "6") != 0)
+ {
+ mycmd = rc_xstrdup ("/sbin/shutdown");
+ myarg = rc_xstrdup ("-r");
+ tmp = rc_xstrdup ("now");
+ execl (mycmd, mycmd, myarg, tmp, NULL);
+ eerrorx ("%s: unable to exec `%s': %s",
+ mycmd, applet, strerror (errno));
+ }
+ }
+ else if (strcmp (newlevel, RC_LEVEL_SHUTDOWN) == 0)
+ {
+ if (! RUNLEVEL ||
+ strcmp (RUNLEVEL, "0") != 0)
+ {
+ mycmd = rc_xstrdup ("/sbin/shutdown");
+#ifdef __linux__
+ myarg = rc_xstrdup ("-h");
+#else
+ myarg = rc_xstrdup ("-p");
+#endif
+ tmp = rc_xstrdup ("now");
+ execl (mycmd, mycmd, myarg, tmp, NULL);
+ eerrorx ("%s: unable to exec `%s': %s",
+ mycmd, applet, strerror (errno));
+ }
+ }
+ }
+
+ /* Export our current softlevel */
+ runlevel = rc_get_runlevel ();
+
+ /* If we're in the default runlevel and ksoftlevel exists, we should use
+ that instead */
+ if (newlevel &&
+ rc_exists (RC_SVCDIR "ksoftlevel") &&
+ strcmp (newlevel, RC_LEVEL_DEFAULT) == 0)
+ {
+ /* 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))
+ {
+ FILE *fp;
+
+ if (! (fp = fopen (RC_SVCDIR "ksoftlevel", "r")))
+ eerror ("fopen `%s': %s", RC_SVCDIR "ksoftlevel",
+ strerror (errno));
+ else
+ {
+ if (fgets (ksoftbuffer, sizeof (ksoftbuffer), fp))
+ {
+ i = strlen (ksoftbuffer) - 1;
+ if (ksoftbuffer[i] == '\n')
+ ksoftbuffer[i] = 0;
+ newlevel = ksoftbuffer;
+ }
+ fclose (fp);
+ }
+ }
+ else
+ 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_set_runlevel (newlevel);
+ setenv ("RC_SOFTLEVEL", newlevel, 1);
+ 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)
+ {
+ tmp = rc_strcatpaths (RC_RUNLEVELDIR, newlevel, NULL);
+ if (! rc_is_dir (tmp))
+ eerrorx ("%s: is not a valid runlevel", newlevel);
+ CHAR_FREE (tmp);
+ }
+
+ /* Load our deptree now */
+ if ((deptree = rc_load_deptree ()) == NULL)
+ eerrorx ("failed to load deptree");
+
+ /* Clean the failed services state dir now */
+ if (rc_is_dir (RC_SVCDIR "failed"))
+ rc_rm_dir (RC_SVCDIR "failed", false);
+
+ mkdir (RC_SVCDIR "/softscripts.new", 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 (rc_is_dir (DEVBOOT))
+ {
+ start_services = rc_ls_dir (NULL, DEVBOOT, RC_LS_INITD);
+ rc_rm_dir (DEVBOOT, true);
+
+ STRLIST_FOREACH (start_services, service, i)
+ if (rc_allow_plug (service))
+ rc_mark_service (service, rc_service_coldplugged);
+ /* We need to dump this list now.
+ This may seem redunant, but only Linux needs this and saves on
+ code bloat. */
+ rc_strlist_free (start_services);
+ start_services = NULL;
+ }
+#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, RC_LEVEL_BOOT) == 0 &&
+ (strcmp (runlevel, RC_LEVEL_SINGLE) == 0 ||
+ strcmp (runlevel, RC_LEVEL_SYSINIT) == 0) &&
+ rc_is_env ("RC_COLDPLUG", "yes"))
+ {
+ /* The net interfaces are easy - they're all in net /dev/net :) */
+ start_services = rc_ls_dir (NULL, "/dev/net", 0);
+ STRLIST_FOREACH (start_services, service, i)
+ {
+ j = (strlen ("net.") + strlen (service) + 1);
+ tmp = rc_xmalloc (sizeof (char *) * j);
+ snprintf (tmp, j, "net.%s", service);
+ if (rc_service_exists (tmp) && rc_allow_plug (tmp))
+ rc_mark_service (tmp, rc_service_coldplugged);
+ CHAR_FREE (tmp);
+ }
+ rc_strlist_free (start_services);
+
+ /* The mice are a little more tricky.
+ If we coldplug anything else, we'll probably do it here. */
+ start_services = rc_ls_dir (NULL, "/dev", 0);
+ STRLIST_FOREACH (start_services, service, i)
+ {
+ if (strncmp (service, "psm", 3) == 0 ||
+ strncmp (service, "ums", 3) == 0)
+ {
+ char *p = service + 3;
+ if (p && isdigit (*p))
+ {
+ j = (strlen ("moused.") + strlen (service) + 1);
+ tmp = rc_xmalloc (sizeof (char *) * j);
+ snprintf (tmp, j, "moused.%s", service);
+ if (rc_service_exists (tmp) && rc_allow_plug (tmp))
+ rc_mark_service (tmp, rc_service_coldplugged);
+ CHAR_FREE (tmp);
+ }
+ }
+ }
+ rc_strlist_free (start_services);
+ start_services = NULL;
+ }
+#endif
+
+ /* Build a list of all services to stop and then work out the
+ correct order for stopping them */
+ stop_services = rc_ls_dir (stop_services, RC_SVCDIR_STARTING, RC_LS_INITD);
+ stop_services = rc_ls_dir (stop_services, RC_SVCDIR_INACTIVE, RC_LS_INITD);
+ stop_services = rc_ls_dir (stop_services, RC_SVCDIR_STARTED, RC_LS_INITD);
+
+ types = rc_strlist_add (NULL, "ineed");
+ types = rc_strlist_add (types, "iuse");
+ types = rc_strlist_add (types, "iafter");
+ deporder = rc_get_depends (deptree, types, stop_services,
+ runlevel, depoptions);
+ rc_strlist_free (stop_services);
+ rc_strlist_free (types);
+ stop_services = deporder;
+ deporder = NULL;
+ types = NULL;
+ rc_strlist_reverse (stop_services);
+
+ /* Load our list of coldplugged services */
+ coldplugged_services = rc_ls_dir (coldplugged_services,
+ RC_SVCDIR_COLDPLUGGED, RC_LS_INITD);
+
+ /* Load our start services now.
+ We have different rules dependent on runlevel. */
+ if (newlevel && strcmp (newlevel, RC_LEVEL_BOOT) == 0)
+ {
+ if (coldplugged_services)
+ {
+ einfon ("Device initiated services:");
+ STRLIST_FOREACH (coldplugged_services, service, i)
+ {
+ printf (" %s", service);
+ start_services = rc_strlist_add (start_services, service);
+ }
+ printf ("\n");
+ }
+ tmp = rc_strcatpaths (RC_RUNLEVELDIR, newlevel ? newlevel : runlevel, NULL);
+ start_services = rc_ls_dir (start_services, tmp, RC_LS_INITD);
+ CHAR_FREE (tmp);
+ }
+ else
+ {
+ /* Store our list of coldplugged services */
+ coldplugged_services = rc_ls_dir (coldplugged_services, RC_SVCDIR_COLDPLUGGED,
+ RC_LS_INITD);
+ 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 */
+ start_services = rc_ls_dir (start_services, RC_RUNLEVELDIR RC_LEVEL_BOOT,
+ RC_LS_INITD);
+ STRLIST_FOREACH (coldplugged_services, service, i)
+ start_services = rc_strlist_add (start_services, service);
+
+ tmp = rc_strcatpaths (RC_RUNLEVELDIR,
+ newlevel ? newlevel : runlevel, NULL);
+ start_services = rc_ls_dir (start_services, tmp, RC_LS_INITD);
+ CHAR_FREE (tmp);
+ }
+ }
+
+ /* Save out softlevel now */
+ if (going_down)
+ rc_set_runlevel (newlevel);
+
+ types = rc_strlist_add (NULL, "needsme");
+ types = rc_strlist_add (types, "usesme");
+ /* 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)
+ {
+ rc_stop_service (service);
+ 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)
+ {
+ if (! newlevel)
+ continue;
+
+ tmp = rc_xmalloc (strlen (service) + strlen (runlevel) + 2);
+ sprintf (tmp, "%s.%s", service, runlevel);
+ conf = rc_strcatpaths (RC_CONFDIR, tmp, NULL);
+ found = rc_exists (conf);
+ CHAR_FREE (conf);
+ CHAR_FREE (tmp);
+ if (! found)
+ {
+ tmp = rc_xmalloc (strlen (service) + strlen (newlevel) + 2);
+ sprintf (tmp, "%s.%s", service, newlevel);
+ conf = rc_strcatpaths (RC_CONFDIR, tmp, NULL);
+ found = rc_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 */
+ stopdeps = rc_strlist_add (stopdeps, service);
+ deporder = rc_get_depends (deptree, types, 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)
+ rc_stop_service (service);
+ }
+ rc_strlist_free (types);
+ types = NULL;
+
+ /* Wait for our services to finish */
+ if (rc_is_env ("RC_PARALLEL_STARTUP", "yes"))
+ wait_for_services ();
+
+ /* Notify the plugins we have finished */
+ rc_plugin_run (rc_hook_runlevel_stop_out, runlevel);
+
+ rmdir (RC_SVCDIR "/softscripts.new");
+
+ /* Store the new runlevel */
+ if (newlevel)
+ {
+ rc_set_runlevel (newlevel);
+ runlevel = 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)
+ {
+ mycmd = rc_xstrdup (HALTSH);
+ myarg = rc_xstrdup (runlevel);
+ execl (mycmd, mycmd, myarg, 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 (rc_exists (INTERACTIVE))
+ unlink (INTERACTIVE);
+ sulogin (false);
+ }
+
+ mkdir (RC_SVCDIR "/softscripts.old", 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_mark_service (service, rc_service_coldplugged);
+
+ /* Order the services to start */
+ types = rc_strlist_add (NULL, "ineed");
+ types = rc_strlist_add (types, "iuse");
+ types = rc_strlist_add (types, "iafter");
+ deporder = rc_get_depends (deptree, types, start_services,
+ runlevel, depoptions);
+ rc_strlist_free (types);
+ types = NULL;
+ rc_strlist_free (start_services);
+ start_services = deporder;
+ deporder = NULL;
+
+ STRLIST_FOREACH (start_services, service, i)
+ {
+ if (rc_service_state (service, rc_service_stopped))
+ {
+ 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;
+ }
+ }
+ rc_start_service (service);
+ }
+ }
+
+ /* Wait for our services to finish */
+ if (rc_is_env ("RC_PARALLEL_STARTUP", "yes"))
+ wait_for_services ();
+
+ rc_plugin_run (rc_hook_runlevel_start_out, runlevel);
+
+ /* Store our interactive status for boot */
+ if (interactive && strcmp (runlevel, RC_LEVEL_BOOT) == 0)
+ mark_interactive ();
+ else
+ {
+ if (rc_exists (INTERACTIVE))
+ unlink (INTERACTIVE);
+ }
+
+ return (EXIT_SUCCESS);
+}
+
diff --git a/src/rc.h b/src/rc.h
new file mode 100644
index 0000000..3ba7cc5
--- /dev/null
+++ b/src/rc.h
@@ -0,0 +1,180 @@
+/*
+ rc.h
+ Header file for external applications to get RC information.
+ Copyright 2007 Gentoo Foundation
+ Released under the GPLv2
+ */
+
+#ifndef __RC_H__
+#define __RC_H__
+
+#include <sys/types.h>
+#include <stdbool.h>
+
+/* Special level names */
+#define RC_LEVEL_SYSINIT "sysinit"
+#define RC_LEVEL_BOOT "boot"
+#define RC_LEVEL_SINGLE "single"
+#define RC_LEVEL_SHUTDOWN "shutdown"
+#define RC_LEVEL_REBOOT "reboot"
+#define RC_LEVEL_DEFAULT "default"
+
+typedef enum
+{
+ rc_service_started,
+ rc_service_stopped,
+ rc_service_starting,
+ rc_service_stopping,
+ rc_service_inactive,
+ rc_service_wasinactive,
+ rc_service_coldplugged,
+ rc_service_failed,
+ rc_service_scheduled,
+ rc_service_crashed
+} rc_service_state_t;
+
+char *rc_resolve_service (const char *service);
+bool rc_service_exists (const char *service);
+bool rc_service_in_runlevel (const char *service, const char *runlevel);
+bool rc_service_state (const char *service, rc_service_state_t state);
+bool rc_mark_service (const char *service, rc_service_state_t state);
+pid_t rc_stop_service (const char *service);
+pid_t rc_start_service (const char *service);
+void rc_schedule_start_service (const char *service,
+ const char *service_to_start);
+char **rc_services_scheduled_by (const char *service);
+void rc_schedule_clear (const char *service);
+bool rc_wait_service (const char *service);
+bool rc_get_service_option (const char *service, const char *option,
+ char *value);
+bool rc_set_service_option (const char *service, const char *option,
+ const char *value);
+void rc_set_service_daemon (const char *service, const char *exec,
+ const char *name, const char *pidfile,
+ bool started);
+bool rc_service_started_daemon (const char *service, const char *exec,
+ int indx);
+
+bool rc_allow_plug (char *service);
+
+char *rc_get_runlevel (void);
+void rc_set_runlevel (const char *runlevel);
+bool rc_runlevel_exists (const char *runlevel);
+char **rc_get_runlevels (void);
+bool rc_runlevel_starting (void);
+bool rc_runlevel_stopping (void);
+bool rc_service_add (const char *runlevel, const char *service);
+bool rc_service_delete (const char *runlevel, const char *service);
+char **rc_services_in_runlevel (const char *runlevel);
+char **rc_services_in_state (rc_service_state_t state);
+char **rc_services_scheduled (const char *service);
+
+/* Find pids based on criteria - free the pointer returned after use */
+pid_t *rc_find_pids (const char *exec, const char *cmd,
+ uid_t uid, pid_t pid);
+/* Checks that all daemons started with start-stop-daemon by the service
+ are still running. If so, return false otherwise true.
+ You should check that the service has been started before calling this. */
+bool rc_service_daemons_crashed (const char *service);
+
+/* Dependency tree structs and functions. */
+typedef struct rc_deptype
+{
+ char *type;
+ char **services;
+ struct rc_deptype *next;
+} rc_deptype_t;
+
+typedef struct rc_depinfo
+{
+ char *service;
+ rc_deptype_t *depends;
+ struct rc_depinfo *next;
+} rc_depinfo_t;
+
+
+/* Options for rc_dep_depends and rc_order_services.
+ When changing runlevels, you should use RC_DEP_START and RC_DEP_STOP for
+ the start and stop lists as we tweak the provided services for this. */
+#define RC_DEP_TRACE 0x01
+#define RC_DEP_STRICT 0x02
+#define RC_DEP_START 0x04
+#define RC_DEP_STOP 0x08
+
+int rc_update_deptree (bool force);
+rc_depinfo_t *rc_load_deptree (void);
+rc_depinfo_t *rc_get_depinfo (rc_depinfo_t *deptree, const char *service);
+rc_deptype_t *rc_get_deptype (rc_depinfo_t *depinfo, const char *type);
+char **rc_get_depends (rc_depinfo_t *deptree, char **types,
+ char **services, const char *runlevel, int options);
+/* List all the services that should be started, in order, the the
+ given runlevel, including sysinit and boot services where
+ approriate.
+ If reboot, shutdown or single are given then we list all the services
+ we that we need to shutdown in order. */
+char **rc_order_services (rc_depinfo_t *deptree, const char *runlevel,
+ int options);
+
+void rc_free_deptree (rc_depinfo_t *deptree);
+
+/* Plugin handler
+ For each plugin loaded we will call it's _name_hook with the below
+ enum and either the runlevel name or service name. For example
+ int _splash_hook (rc_hook_t hook, const char *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. */
+typedef enum
+{
+ rc_hook_runlevel_stop_in = 1,
+ rc_hook_runlevel_stop_out,
+ rc_hook_runlevel_start_in,
+ rc_hook_runlevel_start_out,
+ rc_hook_service_stop_in,
+ rc_hook_service_stop_out,
+ rc_hook_service_start_in,
+ rc_hook_service_start_out
+} rc_hook_t;
+
+/* RC utility functions.
+ Although not directly related to RC in general, they are used by RC
+ itself and the supporting applications. */
+void *rc_xcalloc (size_t n, size_t size);
+void *rc_xmalloc (size_t size);
+void *rc_xrealloc (void *ptr, size_t size);
+char *rc_xstrdup (const char *str);
+
+/* Concat paths adding '/' if needed. */
+char *rc_strcatpaths (const char *path1, const char *paths, ...);
+
+bool rc_is_env (const char *variable, const char *value);
+bool rc_exists (const char *pathname);
+bool rc_is_file (const char *pathname);
+bool rc_is_link (const char *pathname);
+bool rc_is_dir (const char *pathname);
+bool rc_is_exec (const char *pathname);
+
+#define RC_LS_INITD 0x01
+char **rc_ls_dir (char **list, const char *dir, int options);
+
+bool rc_rm_dir (const char *pathname, bool top);
+
+/* Config file functions */
+char **rc_get_list (char **list, const char *file);
+char **rc_get_config (char **list, const char *file);
+char *rc_get_config_entry (char **list, const char *entry);
+
+/* Make an environment list which filters out all unwanted values
+ and loads it up with our RC config */
+char **rc_filter_env (void);
+char **rc_config_env (char **env);
+
+/* Handy functions for dealing with string arrays of char ** */
+char **rc_strlist_add (char **list, const char *item);
+char **rc_strlist_addsort (char **list, const char *item);
+char **rc_strlist_addsortc (char **list, const char *item);
+char **rc_strlist_delete (char **list, const char *item);
+void rc_strlist_reverse (char **list);
+void rc_strlist_free (char **list);
+
+#endif
diff --git a/src/runscript.c b/src/runscript.c
new file mode 100644
index 0000000..bca1195
--- /dev/null
+++ b/src/runscript.c
@@ -0,0 +1,1097 @@
+/*
+ * runscript.c
+ * Handle launching of Gentoo init scripts.
+ *
+ * Copyright 1999-2007 Gentoo Foundation
+ * Distributed under the terms of the GNU General Public License v2
+ */
+
+#include <sys/types.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <dlfcn.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifndef __linux__
+#include <libgen.h>
+#endif
+
+#include "einfo.h"
+#include "rc.h"
+#include "rc-misc.h"
+#include "rc-plugin.h"
+#include "strlist.h"
+
+#define RCSCRIPT_HELP RC_LIBDIR "/sh/rc-help.sh"
+#define SELINUX_LIB RC_LIBDIR "/runscript_selinux.so"
+
+static char *applet = NULL;
+static char *exclusive = NULL;
+static char *mtime_test = NULL;
+static rc_depinfo_t *deptree = NULL;
+static char **services = NULL;
+static char **svclist = NULL;
+static char **tmplist = NULL;
+static char **providelist = NULL;
+static char **types = NULL;
+static char **restart_services = NULL;
+static char **need_services = NULL;
+static char **env = NULL;
+static char *mycmd = NULL;
+static char *myarg1 = NULL;
+static char *myarg2 = 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;
+
+extern char **environ;
+
+#ifdef __linux__
+static void (*selinux_run_init_old) (void);
+static void (*selinux_run_init_new) (int argc, char **argv);
+
+void setup_selinux (int argc, char **argv);
+#endif
+
+#ifdef __linux__
+void setup_selinux (int argc, char **argv)
+{
+ void *lib_handle = NULL;
+
+ lib_handle = dlopen (SELINUX_LIB, RTLD_NOW | RTLD_GLOBAL);
+ if (lib_handle)
+ {
+ /* FIXME: the below code generates the warning
+ ISO C forbids assignment between function pointer and 'void *'
+ which sucks ass
+ http://www.opengroup.org/onlinepubs/009695399/functions/dlsym.html */
+ selinux_run_init_old = dlsym (lib_handle, "selinux_runscript");
+ selinux_run_init_new = dlsym (lib_handle, "selinux_runscript2");
+
+ /* Use new run_init if it rc_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!");
+ }
+}
+#endif
+
+static void handle_signal (int sig)
+{
+ pid_t pid;
+ int status;
+ int serrno = errno;
+
+ switch (sig)
+ {
+ case SIGHUP:
+ sighup = true;
+ break;
+
+ 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));
+ break;
+
+ case SIGINT:
+ case SIGTERM:
+ case SIGQUIT:
+ eerrorx ("%s: caught signal %d, aborting", applet, sig);
+
+ 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 == 0)
+ 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 == NULL || ! rc_exists (mtime_test))
+ return (false);
+
+ if (rc_service_state (applet, rc_service_stopped))
+ return (false);
+
+ if ((mtime = get_mtime (mtime_test, false)) == 0)
+ return (false);
+
+ while (tests[i])
+ {
+ path = rc_strcatpaths (RC_SVCDIR, tests[i], applet, NULL);
+ if (rc_exists (path))
+ {
+ int m = get_mtime (path, false);
+ if (mtime < m && m != 0)
+ {
+ free (path);
+ return (false);
+ }
+ }
+ free (path);
+ i++;
+ }
+
+ return (true);
+}
+
+static void uncoldplug (char *service)
+{
+ char *cold = rc_strcatpaths (RC_SVCDIR "coldplugged", basename (service), NULL);
+ if (rc_exists (cold) && unlink (cold) != 0)
+ eerror ("%s: unlink `%s': %s", applet, cold, strerror (errno));
+ free (cold);
+}
+
+static void cleanup (void)
+{
+ /* Flush our buffered output if any */
+ eflush ();
+
+ if (hook_out)
+ rc_plugin_run (hook_out, applet);
+ rc_plugin_unload ();
+
+ if (deptree)
+ rc_free_deptree (deptree);
+ if (services)
+ rc_strlist_free (services);
+ if (types)
+ rc_strlist_free (types);
+ if (svclist)
+ rc_strlist_free (svclist);
+ if (providelist)
+ rc_strlist_free (providelist);
+ if (restart_services)
+ rc_strlist_free (restart_services);
+ if (need_services)
+ rc_strlist_free (need_services);
+ if (mycmd)
+ free (mycmd);
+ if (myarg1)
+ free (myarg1);
+ if (myarg2)
+ free (myarg2);
+ if (ibsave)
+ free (ibsave);
+
+ if (in_control ())
+ {
+ if (rc_service_state (applet, rc_service_starting))
+ {
+ if (rc_service_state (applet, rc_service_wasinactive))
+ rc_mark_service (applet, rc_service_inactive);
+ else
+ rc_mark_service (applet, rc_service_stopped);
+ }
+ else if (rc_service_state (applet, rc_service_stopping))
+ {
+ /* If the we're shutting down, do it cleanly */
+ if ((softlevel && rc_runlevel_stopping () &&
+ (strcmp (softlevel, RC_LEVEL_SHUTDOWN) == 0 ||
+ strcmp (softlevel, RC_LEVEL_REBOOT) == 0)) ||
+ ! rc_service_state (applet, rc_service_wasinactive))
+ rc_mark_service (applet, rc_service_stopped);
+ else
+ rc_mark_service (applet, rc_service_inactive);
+ }
+ if (exclusive && rc_exists (exclusive))
+ unlink (exclusive);
+ }
+
+ if (env)
+ rc_strlist_free (env);
+
+ if (mtime_test)
+ {
+ unlink (mtime_test);
+ free (mtime_test);
+ }
+ if (exclusive)
+ free (exclusive);
+
+ if (applet)
+ free (applet);
+}
+
+static bool svc_exec (const char *service, const char *arg1, const char *arg2)
+{
+ int status = 0;
+ pid_t pid;
+
+ /* We need to disable our child signal handler now so we block
+ until our script returns. */
+ signal (SIGCHLD, NULL);
+
+ pid = fork();
+
+ if (pid == -1)
+ eerrorx ("%s: fork: %s", service, strerror (errno));
+ if (pid == 0)
+ {
+ mycmd = rc_xstrdup (service);
+ myarg1 = rc_xstrdup (arg1);
+ if (arg2)
+ myarg2 = rc_xstrdup (arg2);
+
+ if (rc_exists (RC_SVCDIR "runscript.sh"))
+ {
+ execl (RC_SVCDIR "runscript.sh", mycmd, mycmd, myarg1, myarg2, NULL);
+ eerrorx ("%s: exec `" RC_SVCDIR "runscript.sh': %s",
+ service, strerror (errno));
+ }
+ else
+ {
+ execl (RC_LIBDIR "sh/runscript.sh", mycmd, mycmd, myarg1, myarg2, NULL);
+ eerrorx ("%s: exec `" RC_LIBDIR "sh/runscript.sh': %s",
+ service, strerror (errno));
+ }
+ }
+
+ do
+ {
+ if (waitpid (pid, &status, 0) < 0)
+ {
+ if (errno != ECHILD)
+ eerror ("waitpid: %s", strerror (errno));
+ break;
+ }
+ } while (! WIFEXITED (status) && ! WIFSIGNALED (status));
+
+ /* Done, so restore the signal handler */
+ signal (SIGCHLD, handle_signal);
+
+ if (WIFEXITED (status))
+ return (WEXITSTATUS (status) == 0 ? true : false);
+
+ return (false);
+}
+
+static rc_service_state_t svc_status (const char *service)
+{
+ char status[10];
+ int (*e) (const char *fmt, ...) = &einfo;
+
+ rc_service_state_t retval = rc_service_stopped;
+
+ if (rc_service_state (service, rc_service_stopping))
+ {
+ snprintf (status, sizeof (status), "stopping");
+ e = &ewarn;
+ retval = rc_service_stopping;
+ }
+ else if (rc_service_state (service, rc_service_starting))
+ {
+ snprintf (status, sizeof (status), "starting");
+ e = &ewarn;
+ retval = rc_service_starting;
+ }
+ else if (rc_service_state (service, rc_service_inactive))
+ {
+ snprintf (status, sizeof (status), "inactive");
+ e = &ewarn;
+ retval = rc_service_inactive;
+ }
+ else if (rc_service_state (service, rc_service_crashed))
+ {
+ snprintf (status, sizeof (status), "crashed");
+ e = &eerror;
+ retval = rc_service_crashed;
+ }
+ else if (rc_service_state (service, rc_service_started))
+ {
+ snprintf (status, sizeof (status), "started");
+ retval = rc_service_started;
+ }
+ else
+ snprintf (status, sizeof (status), "stopped");
+
+ e ("status: %s", status);
+ return (retval);
+}
+
+static void make_exclusive (const char *service)
+{
+ 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, 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, NULL);
+ i = strlen (path) + 16;
+ mtime_test = rc_xmalloc (sizeof (char *) * i);
+ snprintf (mtime_test, i, "%s.%d", path, getpid ());
+ free (path);
+
+ if (rc_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 ()
+{
+ char *service;
+ int i;
+
+ 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);
+
+ STRLIST_FOREACH (tmplist, service, i)
+ restart_services = rc_strlist_addsort (restart_services, service);
+
+ rc_strlist_free (tmplist);
+ tmplist = NULL;
+}
+
+static void svc_start (const char *service, bool deps)
+{
+ bool started;
+ bool background = false;
+ char *svc;
+ char *svc2;
+ int i;
+ int j;
+ int depoptions = RC_DEP_TRACE;
+
+ if (rc_is_env ("RC_STRICT_DEPEND", "yes"))
+ depoptions |= RC_DEP_STRICT;
+
+ if (rc_is_env ("IN_HOTPLUG", "1") || in_background)
+ {
+ if (! rc_service_state (service, rc_service_inactive))
+ exit (EXIT_FAILURE);
+ background = true;
+ }
+
+ if (rc_service_state (service, rc_service_started))
+ ewarnx ("WARNING: %s has already been started", applet);
+ else if (rc_service_state (service, rc_service_starting))
+ ewarnx ("WARNING: %s is already starting", applet);
+ else if (rc_service_state (service, rc_service_stopping))
+ ewarnx ("WARNING: %s is stopping", applet);
+ else if (rc_service_state (service, rc_service_inactive) && ! background)
+ ewarnx ("WARNING: %s has already started, but is inactive", applet);
+
+ if (! rc_mark_service (service, rc_service_starting))
+ eerrorx ("ERROR: %s has been started by something else", applet);
+
+ make_exclusive (service);
+
+ if (deps)
+ {
+ if (! deptree && ((deptree = rc_load_deptree ()) == NULL))
+ eerrorx ("failed to load deptree");
+
+ rc_strlist_free (types);
+ types = rc_strlist_add (NULL, "broken");
+ rc_strlist_free (svclist);
+ svclist = rc_strlist_add (NULL, applet);
+ rc_strlist_free (services);
+ services = rc_get_depends (deptree, types, svclist, 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 (types);
+ types = rc_strlist_add (NULL, "ineed");
+ rc_strlist_free (need_services);
+ need_services = rc_get_depends (deptree, types, svclist,
+ softlevel, depoptions);
+ types = rc_strlist_add (types, "iuse");
+ if (! rc_runlevel_starting ())
+ {
+ services = rc_get_depends (deptree, types, svclist,
+ softlevel, depoptions);
+ STRLIST_FOREACH (services, svc, i)
+ if (rc_service_state (svc, rc_service_stopped))
+ rc_start_service (svc);
+
+ rc_strlist_free (services);
+ }
+
+ /* Now wait for them to start */
+ types = rc_strlist_add (types, "iafter");
+ services = rc_get_depends (deptree, types, svclist,
+ softlevel, depoptions);
+
+ /* We use tmplist to hold our scheduled by list */
+ rc_strlist_free (tmplist);
+ tmplist = NULL;
+
+ STRLIST_FOREACH (services, svc, i)
+ {
+ if (rc_service_state (svc, rc_service_started))
+ continue;
+ if (! rc_wait_service (svc))
+ { eerror ("%s: timed out waiting for %s", applet, svc);
+ system ("ps ax > /tmp/$SVCNAME.waiting"); }
+ if (rc_service_state (svc, rc_service_started))
+ continue;
+
+ STRLIST_FOREACH (need_services, svc2, j)
+ if (strcmp (svc, svc2) == 0)
+ {
+ if (rc_service_state (svc, rc_service_inactive) ||
+ rc_service_state (svc, rc_service_wasinactive))
+ tmplist = 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_mark_service (service, rc_service_stopped);
+ unlink_mtime_test ();
+
+ rc_strlist_free (types);
+ types = rc_strlist_add (NULL, "iprovide");
+ STRLIST_FOREACH (tmplist, svc, i)
+ {
+ rc_schedule_start_service (svc, service);
+
+ rc_strlist_free (svclist);
+ svclist = rc_strlist_add (NULL, svc);
+ rc_strlist_free (providelist);
+ providelist = rc_get_depends (deptree, types, svclist,
+ softlevel, depoptions);
+ STRLIST_FOREACH (providelist, svc2, j)
+ rc_schedule_start_service (svc2, service);
+
+ len += strlen (svc) + 2;
+ n++;
+ }
+
+ tmp = rc_xmalloc (sizeof (char *) * len + 5);
+ p = tmp;
+ STRLIST_FOREACH (tmplist, svc, i)
+ {
+ if (i > 1)
+ {
+ if (i == n - 1)
+ p += sprintf (p, " or ");
+ else
+ p += sprintf (p, ", ");
+ }
+ p += sprintf (p, "%s", svc);
+ }
+ ewarnx ("WARNING: %s is scheduled to start when %s has started",
+ applet, tmp);
+ }
+
+ rc_strlist_free (services);
+ services = NULL;
+ rc_strlist_free (types);
+ types = NULL;
+ rc_strlist_free (svclist);
+ svclist = NULL;
+ }
+
+ if (ibsave)
+ setenv ("IN_BACKGROUND", ibsave, 1);
+ rc_plugin_run (rc_hook_service_start_in, applet);
+ hook_out = rc_hook_service_start_out;
+ started = svc_exec (service, "start", NULL);
+ if (ibsave)
+ unsetenv ("IN_BACKGROUND");
+
+ if (in_control ())
+ {
+ if (! started)
+ {
+ if (rc_service_state (service, rc_service_wasinactive))
+ rc_mark_service (service, rc_service_inactive);
+ else
+ {
+ rc_mark_service (service, rc_service_stopped);
+ if (rc_runlevel_starting ())
+ rc_mark_service (service, rc_service_failed);
+ }
+ eerrorx ("ERROR: %s failed to start", applet);
+ }
+
+ rc_mark_service (service, rc_service_started);
+ unlink_mtime_test ();
+
+ hook_out = 0;
+ rc_plugin_run (rc_hook_service_start_out, applet);
+ }
+ else
+ {
+ if (rc_service_state (service, rc_service_inactive))
+ ewarn ("WARNING: %s has started, but is inactive", applet);
+ else
+ ewarn ("WARNING: %s not under our control, aborting", applet);
+ }
+
+ /* 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_start_service (svc);
+ rc_strlist_free (services);
+ services = NULL;
+
+ /* Do the same for any services we provide */
+ rc_strlist_free (types);
+ types = rc_strlist_add (NULL, "iprovide");
+ rc_strlist_free (svclist);
+ svclist = rc_strlist_add (NULL, applet);
+ rc_strlist_free (tmplist);
+ tmplist = rc_get_depends (deptree, types, svclist, softlevel, depoptions);
+
+ 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_start_service (svc);
+ }
+}
+
+static void svc_stop (const char *service, bool deps)
+{
+ bool stopped;
+
+ if (rc_runlevel_stopping () &&
+ rc_service_state (service, rc_service_failed))
+ exit (EXIT_FAILURE);
+
+ if (rc_is_env ("IN_HOTPLUG", "1") || in_background)
+ if (! rc_service_state (service, rc_service_started))
+ exit (EXIT_FAILURE);
+
+ if (rc_service_state (service, rc_service_stopped))
+ ewarnx ("WARNING: %s is already stopped", applet);
+ else if (rc_service_state (service, rc_service_stopping))
+ ewarnx ("WARNING: %s is already stopping", applet);
+
+ if (! rc_mark_service (service, rc_service_stopping))
+ eerrorx ("ERROR: %s has been stopped by something else", applet);
+
+ make_exclusive (service);
+
+ if (! rc_runlevel_stopping () &&
+ rc_service_in_runlevel (service, RC_LEVEL_BOOT))
+ ewarn ("WARNING: you are stopping a boot service");
+
+ if (deps || ! rc_service_state (service, rc_service_wasinactive))
+ {
+ int depoptions = RC_DEP_TRACE;
+ char *svc;
+ int i;
+
+ if (rc_is_env ("RC_STRICT_DEPEND", "yes"))
+ depoptions |= RC_DEP_STRICT;
+
+ if (! deptree && ((deptree = rc_load_deptree ()) == NULL))
+ eerrorx ("failed to load deptree");
+
+ rc_strlist_free (types);
+ types = rc_strlist_add (NULL, "needsme");
+ rc_strlist_free (svclist);
+ svclist = rc_strlist_add (NULL, applet);
+ rc_strlist_free (tmplist);
+ tmplist = NULL;
+ rc_strlist_free (services);
+ services = rc_get_depends (deptree, types, svclist,
+ softlevel, depoptions);
+ rc_strlist_reverse (services);
+ STRLIST_FOREACH (services, svc, i)
+ {
+ if (rc_service_state (svc, rc_service_started) ||
+ rc_service_state (svc, rc_service_inactive))
+ {
+ rc_wait_service (svc);
+ if (rc_service_state (svc, rc_service_started) ||
+ rc_service_state (svc, rc_service_inactive))
+ {
+ rc_stop_service (svc);
+ tmplist = 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 */
+ rc_wait_service (svc);
+ if (! rc_service_state (svc, rc_service_stopped))
+ {
+ if (rc_runlevel_stopping ())
+ rc_mark_service (svc, 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 */
+ types = rc_strlist_add (types, "usesme");
+ types = rc_strlist_add (types, "ibefore");
+ services = rc_get_depends (deptree, types, svclist,
+ softlevel, depoptions);
+ STRLIST_FOREACH (services, svc, i)
+ {
+ if (rc_service_state (svc, rc_service_stopped))
+ continue;
+ rc_wait_service (svc);
+ }
+
+ rc_strlist_free (services);
+ services = NULL;
+ rc_strlist_free (types);
+ types = NULL;
+ }
+
+ if (ibsave)
+ setenv ("IN_BACKGROUND", ibsave, 1);
+ rc_plugin_run (rc_hook_service_stop_in, applet);
+ hook_out = rc_hook_service_stop_out;
+ stopped = svc_exec (service, "stop", NULL);
+ if (ibsave)
+ unsetenv ("IN_BACKGROUND");
+
+ if (! in_control ())
+ ewarnx ("WARNING: %s not under our control, aborting", applet);
+
+ if (! stopped)
+ {
+ if (rc_service_state (service, rc_service_wasinactive))
+ rc_mark_service (service, rc_service_inactive);
+ else
+ rc_mark_service (service, rc_service_stopped);
+ eerrorx ("ERROR: %s failed to stop", applet);
+ }
+
+ if (in_background)
+ rc_mark_service (service, rc_service_inactive);
+ else
+ rc_mark_service (service, rc_service_stopped);
+
+ unlink_mtime_test ();
+ hook_out = 0;
+ rc_plugin_run (rc_hook_service_stop_out, applet);
+}
+
+static void svc_restart (const char *service, bool deps)
+{
+ char *svc;
+ int i;
+ bool inactive = false;
+
+ /* 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)
+ {
+ if (rc_service_state (service, rc_service_started) ||
+ rc_service_state (service, rc_service_inactive))
+ svc_exec (service, "stop", "start");
+ else
+ svc_exec (service, "start", NULL);
+ return;
+ }
+
+ if (! rc_service_state (service, rc_service_stopped))
+ {
+ get_started_services ();
+ svc_stop (service, deps);
+
+ /* Flush our buffered output if any */
+ eflush ();
+ }
+
+ svc_start (service, deps);
+
+ inactive = rc_service_state (service, rc_service_inactive);
+ if (! inactive)
+ inactive = rc_service_state (service, rc_service_wasinactive);
+
+ if (inactive ||
+ rc_service_state (service, rc_service_starting) ||
+ rc_service_state (service, rc_service_started))
+ {
+ STRLIST_FOREACH (restart_services, svc, i)
+ {
+ if (rc_service_state (svc, rc_service_stopped))
+ {
+ if (inactive)
+ {
+ rc_schedule_start_service (service, svc);
+ ewarn ("WARNING: %s is scheduled to started when %s has started",
+ svc, basename (service));
+ }
+ else
+ rc_start_service (svc);
+ }
+ }
+ }
+}
+
+int main (int argc, char **argv)
+{
+ const char *service = argv[1];
+ int i;
+ bool deps = true;
+ bool doneone = false;
+ char pid[16];
+ int retval;
+ bool ifstarted = false;
+
+ applet = strdup (basename (service));
+ atexit (cleanup);
+
+ /* Show help if insufficient args */
+ if (argc < 3)
+ {
+ execl (RCSCRIPT_HELP, RCSCRIPT_HELP, service, NULL);
+ eerrorx ("%s: failed to exec `" RCSCRIPT_HELP "': %s",
+ applet, strerror (errno));
+ }
+
+#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 (rc_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, NULL);
+ symlink (service, tmp);
+ exit (EXIT_FAILURE);
+ }
+#endif
+
+ if ((softlevel = getenv ("RC_SOFTLEVEL")) == NULL)
+ {
+ /* Ensure our environment is pure
+ Also, add our configuration to it */
+ env = rc_filter_env ();
+ env = rc_config_env (env);
+
+ 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 = rc_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_get_runlevel ();
+
+ /* If not called from RC or another service then don't be parallel */
+ unsetenv ("RC_PARALLEL_STARTUP");
+ }
+
+ setenv ("RC_ELOG", 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);
+
+ if (rc_is_env ("RC_PARALLEL_STARTUP", "yes"))
+ {
+ char ebname[PATH_MAX];
+ char *eb;
+
+ snprintf (ebname, sizeof (ebname), "%s.%s", applet, pid);
+ eb = rc_strcatpaths (RC_SVCDIR "ebuffer", ebname, NULL);
+ setenv ("RC_EBUFFER", eb, 1);
+ free (eb);
+ }
+
+ /* 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"))
+ {
+ in_background = rc_is_env ("IN_BACKGROUND", "true");
+ ibsave = strdup (getenv ("IN_BACKGROUND"));
+ unsetenv ("IN_BACKGROUND");
+
+ /* Don't hang around */
+ if (in_background)
+ setenv ("RC_PARALLEL_STARTUP", "yes", 1);
+ }
+
+#ifdef __linux__
+ /* Ok, we are ready to go, so setup selinux if applicable */
+ setup_selinux (argc, argv);
+#endif
+
+ /* Right then, parse any options there may be */
+ for (i = 2; i < argc; i++)
+ {
+ if (strlen (argv[i]) < 2 || argv[i][0] != '-' || argv[i][1] != '-')
+ continue;
+
+ if (strcmp (argv[i], "--debug") == 0)
+ setenv ("RC_DEBUG", "yes", 1);
+ else if (strcmp (argv[i], "--help") == 0)
+ {
+ execl (RCSCRIPT_HELP, RCSCRIPT_HELP, service, NULL);
+ eerrorx ("%s: failed to exec `" RCSCRIPT_HELP "': %s",
+ applet, strerror (errno));
+ }
+ else if (strcmp (argv[i],"--ifstarted") == 0)
+ ifstarted = true;
+ else if (strcmp (argv[i], "--nocolour") == 0 ||
+ strcmp (argv[i], "--nocolor") == 0)
+ setenv ("RC_NOCOLOR", "yes", 1);
+ else if (strcmp (argv[i], "--nodeps") == 0)
+ deps = false;
+ else if (strcmp (argv[i], "--quiet") == 0)
+ setenv ("RC_QUIET", "yes", 1);
+ else if (strcmp (argv[i], "--verbose") == 0)
+ setenv ("RC_VERBOSE", "yes", 1);
+ else if (strcmp (argv[i], "--version") == 0)
+ printf ("version me\n");
+ else
+ eerror ("%s: unknown option `%s'", applet, argv[i]);
+ }
+
+ if (ifstarted && ! rc_service_state (applet, rc_service_started))
+ {
+ if (! rc_is_env("RC_QUIET", "yes"))
+ eerror ("ERROR: %s is not started", applet);
+ exit (EXIT_FAILURE);
+ }
+
+ if (rc_is_env ("IN_HOTPLUG", "1"))
+ {
+ if (! rc_is_env ("RC_HOTPLUG", "yes") || ! rc_allow_plug (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;
+ for (i = 2; i < argc; i++)
+ {
+ /* Abort on a sighup here */
+ if (sighup)
+ exit (EXIT_FAILURE);
+
+ if (strlen (argv[i]) < 2 ||
+ (argv[i][0] == '-' && argv[i][1] == '-'))
+ continue;
+
+ /* 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", argv[i], 1);
+
+ doneone = true;
+ if (strcmp (argv[i], "conditionalrestart") == 0 ||
+ strcmp (argv[i], "condrestart") == 0)
+ {
+ if (rc_service_state (service, rc_service_started))
+ svc_restart (service, deps);
+ }
+ else if (strcmp (argv[i], "restart") == 0)
+ svc_restart (service, deps);
+ else if (strcmp (argv[i], "start") == 0)
+ svc_start (service, deps);
+ else if (strcmp (argv[i], "status") == 0)
+ {
+ rc_service_state_t r = svc_status (service);
+ retval = (int) r;
+ }
+ else if (strcmp (argv[i], "stop") == 0)
+ {
+ if (in_background)
+ get_started_services ();
+ else if (! rc_runlevel_stopping ())
+ uncoldplug (applet);
+
+ svc_stop (service, deps);
+
+ if (in_background &&
+ rc_service_state (service, rc_service_inactive))
+ {
+ char *svc;
+ int j;
+ STRLIST_FOREACH (restart_services, svc, j)
+ if (rc_service_state (svc, rc_service_stopped))
+ rc_schedule_start_service (service, svc);
+ }
+ }
+ else if (strcmp (argv[i], "zap") == 0)
+ {
+ einfo ("Manually resetting %s to stopped state", applet);
+ rc_mark_service (applet, rc_service_stopped);
+ uncoldplug (applet);
+ }
+ else if (strcmp (argv[i], "help") == 0)
+ {
+ execl (RCSCRIPT_HELP, RCSCRIPT_HELP, service, "help", NULL);
+ eerrorx ("%s: failed to exec `" RCSCRIPT_HELP "': %s",
+ applet, strerror (errno));
+ }
+ else
+ svc_exec (service, argv[i], NULL);
+
+ /* Flush our buffered output if any */
+ eflush ();
+
+ /* We should ensure this list is empty after an action is done */
+ rc_strlist_free (restart_services);
+ restart_services = NULL;
+ }
+
+ if (! doneone)
+ {
+ execl (RCSCRIPT_HELP, RCSCRIPT_HELP, service, NULL);
+ eerrorx ("%s: failed to exec `" RCSCRIPT_HELP "': %s",
+ applet, strerror (errno));
+ }
+
+ return (retval);
+}
diff --git a/src/splash.c b/src/splash.c
new file mode 100644
index 0000000..3a4edfd
--- /dev/null
+++ b/src/splash.c
@@ -0,0 +1,130 @@
+/*
+ splash.c
+
+ Splash plugin for the Gentoo RC sytsem.
+ splashutils needs to be re-written to support our new system.
+ Until then, we provide this compatible module which calls the
+ legacy bash scripts which is nasty. And slow.
+
+ For any themes that use scripts, such as the live-cd theme,
+ they will have to source /sbin/splash-functions.sh themselves like so
+
+ if ! type splash >/dev/null 2>/dev/null ; then
+ . /sbin/splash-functions.sh
+ fi
+
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <rc.h>
+
+#ifndef LIBDIR
+# define LIBDIR "lib"
+#endif
+
+#define SPLASH_CACHEDIR "/" LIBDIR "/splash/cache"
+
+#define SPLASH_CMD "bash -c 'export SOFTLEVEL='%s'; export BOOTLEVEL=${RC_BOOTLEVEL}; export DEFAULTLEVEL=${RC_DEFAULTLEVEL}; export svcdir=${RC_SVCDIR}; add_suffix() { echo \"$@\"; }; . /etc/init.d/functions.sh; . /sbin/splash-functions.sh; splash %s %s %s'"
+
+int _splash_hook (rc_hook_t hook, const char *name);
+
+static int _do_splash (const char *cmd, const char *arg1, const char *arg2)
+{
+ char *c;
+ int l;
+ char *soft = getenv ("RC_SOFTLEVEL");
+
+ if (! cmd || ! soft)
+ return (-1);
+
+ l = strlen (SPLASH_CMD) + strlen (soft) + strlen (cmd);
+ if (arg1)
+ l += strlen (arg1);
+ if (arg2)
+ l += strlen (arg2);
+ c = malloc (sizeof (char *) * l);
+ if (! c)
+ return (-1);
+
+ snprintf (c, l, SPLASH_CMD,
+ arg1 ? strcmp (arg1, RC_LEVEL_SYSINIT) == 0 ? RC_LEVEL_BOOT : soft : soft,
+ cmd, arg1 ? arg1 : "", arg2 ? arg2 : "");
+ l = system (c);
+ free (c);
+ return (l);
+}
+
+int _splash_hook (rc_hook_t hook, const char *name)
+{
+ switch (hook)
+ {
+ case rc_hook_runlevel_stop_in:
+ if (strcmp (name, RC_LEVEL_SYSINIT) != 0)
+ return (_do_splash ("rc_init", name, NULL));
+ break;
+ case rc_hook_runlevel_start_out:
+ if (strcmp (name, RC_LEVEL_SYSINIT) == 0)
+ return (_do_splash ("rc_init", name, NULL));
+ else
+ return (_do_splash ("rc_exit", name, NULL));
+ default: ;
+ }
+
+ /* We don't care about splash unless we're changing runlevels */
+ if (! rc_runlevel_starting () &&
+ ! rc_runlevel_stopping ())
+ return (0);
+
+ switch (hook)
+ {
+ case rc_hook_service_stop_in:
+ /* We need to stop localmount from unmounting our cache dir.
+ Luckily plugins can add to the unmount list. */
+ if (name && strcmp (name, "localmount") == 0)
+ {
+ char *umounts = getenv ("RC_NO_UMOUNTS");
+ char *new;
+ int i = strlen (SPLASH_CACHEDIR) + 1;
+
+ if (umounts)
+ i += strlen (umounts) + 1;
+
+ new = malloc (sizeof (char *) * i);
+ if (new)
+ {
+ if (umounts)
+ snprintf (new, i, "%s:%s", umounts, SPLASH_CACHEDIR);
+ else
+ snprintf (new, i, "%s", SPLASH_CACHEDIR);
+ }
+
+ /* We unsetenv first as some libc's leak memory if we overwrite
+ a var with a bigger value */
+ if (umounts)
+ unsetenv ("RC_NO_UMOUNTS");
+ setenv ("RC_NO_UMOUNTS", new, 1);
+
+ free (new);
+ }
+ return (_do_splash ("svc_stop", name, NULL));
+ case rc_hook_service_stop_out:
+ if (rc_service_state (name, rc_service_stopped))
+ return (_do_splash ("svc_stopped", name, "0"));
+ else
+ return (_do_splash ("svc_started", name, "1"));
+ case rc_hook_service_start_in:
+ return (_do_splash ("svc_start", name, NULL));
+ case rc_hook_service_start_out:
+ if (rc_service_state (name, rc_service_stopped))
+ return (_do_splash ("svc_started", name, "1"));
+ else
+ return (_do_splash ("svc_started", name, "0"));
+ default: ;
+ }
+
+ return (0);
+}
diff --git a/src/start-stop-daemon.c b/src/start-stop-daemon.c
new file mode 100644
index 0000000..e5dae78
--- /dev/null
+++ b/src/start-stop-daemon.c
@@ -0,0 +1,1047 @@
+/*
+ start-stop-daemon
+ Starts, stops, tests and signals daemons
+ Copyright 2007 Gentoo Foundation
+ Released under the GPLv2
+
+ 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.
+ */
+
+#define POLL_INTERVAL 20000
+#define START_WAIT 100000
+
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/termios.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <grp.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef HAVE_PAM
+#include <security/pam_appl.h>
+
+/* We are not supporting authentication conversations */
+static struct pam_conv conv = { NULL, NULL} ;
+#endif
+
+#include "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 char *progname;
+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", progname, 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", progname, 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'", progname,
+ 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'", progname, 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 = rc_xmalloc (sizeof (schedulelist_t));
+ schedule->gotolist = NULL;
+
+ if (count == 0)
+ {
+ schedule->type = schedule_signal;
+ schedule->value = default_signal;
+ schedule->next = rc_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", progname);
+ }
+ 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", progname);
+
+ 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",
+ progname);
+
+ repeatat = next;
+ continue;
+ }
+
+ if (string)
+ {
+ next->next = rc_xmalloc (sizeof (schedulelist_t));
+ next = next->next;
+ next->gotolist = NULL;
+ }
+ }
+
+ if (repeatat)
+ {
+ next->next = rc_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", progname, pidfile, strerror (errno));
+ return (-1);
+ }
+
+ if (fscanf (fp, "%d", &pid) != 1)
+ {
+ if (! quiet)
+ eerror ("%s: no pid found in `%s'", progname, 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);
+
+ if ((pids = rc_find_pids (exec, cmd, uid, pid)) == NULL)
+ 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 (! killed)
+ {
+ if (! quiet)
+ eerror ("%s: failed to send signal %d to PID %d: %s",
+ progname, sig, pids[i], strerror (errno));
+ if (verbose)
+ eend (1, NULL);
+ nkilled = -1;
+ }
+ else
+ {
+ if (verbose)
+ eend (0, NULL);
+ 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;
+ struct timeval tv;
+ struct timeval now;
+ struct timeval stopat;
+
+ 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", progname);
+ }
+ return (tkilled);
+ }
+ else if (nkilled == -1)
+ return (0);
+
+ tkilled += nkilled;
+ break;
+ case schedule_timeout:
+ if (item->value < 1)
+ {
+ item = NULL;
+ break;
+ }
+
+ if (gettimeofday (&stopat, NULL) != 0)
+ {
+ eerror ("%s: gettimeofday: %s", progname, strerror (errno));
+ return (0);
+ }
+
+ stopat.tv_sec += item->value;
+ while (1)
+ {
+ if ((nrunning = do_stop (exec, cmd, pidfile,
+ uid, 0, true, false, true)) == 0)
+ return (true);
+
+ tv.tv_sec = 0;
+ tv.tv_usec = POLL_INTERVAL;
+ if (select (0, 0, 0, 0, &tv) < 0)
+ {
+ if (errno == EINTR)
+ eerror ("%s: caught an interupt", progname);
+ else
+ eerror ("%s: select: %s", progname, strerror (errno));
+ return (0);
+ }
+
+ if (gettimeofday (&now, NULL) != 0)
+ {
+ eerror ("%s: gettimeofday: %s", progname, strerror (errno));
+ return (0);
+ }
+ if (timercmp (&now, &stopat, >))
+ break;
+ }
+ break;
+
+ default:
+ eerror ("%s: invalid schedule item `%d'", progname, 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", progname, nrunning);
+ else
+ eerror ("%s: %d process(es) refused to stop", progname, nrunning);
+ }
+
+ return (-nrunning);
+}
+
+static void handle_signal (int sig)
+{
+ int pid;
+ int status;
+ int serrno = errno;
+
+ switch (sig)
+ {
+ case SIGINT:
+ case SIGTERM:
+ case SIGQUIT:
+ eerrorx ("%s: caught signal %d, aborting", progname, sig);
+
+ case SIGCHLD:
+ while (1)
+ {
+ if ((pid = waitpid (-1, &status, WNOHANG)) < 0)
+ {
+ if (errno != ECHILD)
+ eerror ("%s: waitpid: %s", progname, strerror (errno));
+ break;
+ }
+ }
+ break;
+
+ default:
+ eerror ("%s: caught unknown signal %d", progname, sig);
+ }
+
+ /* Restore errno */
+ errno = serrno;
+}
+
+int main (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
+
+ static struct option longopts[] = {
+ { "stop", 0, NULL, 'K'},
+ { "nicelevel", 1, NULL, 'N'},
+ { "retry", 1, NULL, 'R'},
+ { "start", 0, NULL, 'S'},
+ { "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'},
+ { "quiet", 0, NULL, 'q'},
+ { "signal", 1, NULL, 's'},
+ { "test", 0, NULL, 't'},
+ { "user", 1, NULL, 'u'},
+ { "chroot", 1, NULL, 'r'},
+ { "verbose", 0, NULL, 'v'},
+ { "exec", 1, NULL, 'x'},
+ { "stdout", 1, NULL, '1'},
+ { "stderr", 1, NULL, '2'},
+ { NULL, 0, NULL, 0}
+ };
+ int c;
+ bool start = false;
+ bool stop = false;
+ bool oknodo = false;
+ bool test = false;
+ bool quiet = false;
+ bool verbose = false;
+ char *exec = NULL;
+ char *cmd = NULL;
+ char *pidfile = NULL;
+ int sig = SIGTERM;
+ uid_t uid = 0;
+ int nicelevel = 0;
+ bool background = false;
+ bool makepidfile = false;
+ uid_t ch_uid = 0;
+ gid_t ch_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;
+ struct timeval tv;
+ int i;
+ char *svcname = getenv ("SVCNAME");
+ char *env;
+
+ progname = argv[0];
+ atexit (cleanup);
+
+ signal (SIGINT, handle_signal);
+ signal (SIGQUIT, handle_signal);
+ signal (SIGTERM, handle_signal);
+
+ while ((c = getopt_long (argc, argv,
+ "KN:R:Sbc:d:g:mn:op:qs:tu:r:vx:1:2:",
+ longopts, (int *) 0)) != -1)
+ switch (c)
+ {
+ case 'K': /* --stop */
+ stop = true;
+ break;
+
+ case 'N': /* --nice */
+ if (sscanf (optarg, "%d", &nicelevel) != 1)
+ eerrorx ("%s: invalid nice level `%s'", progname, optarg);
+ break;
+
+ case 'R': /* --retry <schedule>|<timeout> */
+ parse_schedule (optarg, sig);
+ break;
+
+ case 'S': /* --start */
+ start = true;
+ break;
+
+ case 'b': /* --background */
+ background = true;
+ break;
+
+ case 'c': /* --chuid <username>|<uid> */
+ /* we copy the string just in case we need the
+ * argument later. */
+ {
+ char *p = optarg;
+ char *cu = strsep (&p, ":");
+ changeuser = strdup (cu);
+ if (sscanf (cu, "%d", &tid) != 1)
+ {
+ struct passwd *pw = getpwnam (cu);
+ if (! pw)
+ eerrorx ("%s: user `%s' not found", progname, cu);
+ ch_uid = pw->pw_uid;
+ }
+ else
+ ch_uid = tid;
+ if (p)
+ {
+ char *cg = strsep (&p, ":");
+ if (sscanf (cg, "%d", &tid) != 1)
+ {
+ struct group *gr = getgrnam (cg);
+ if (! gr)
+ eerrorx ("%s: group `%s' not found", progname, cg);
+ ch_gid = gr->gr_gid;
+ }
+ else
+ ch_gid = tid;
+ }
+ }
+ break;
+
+ case 'd': /* --chdir /new/dir */
+ ch_dir = optarg;
+ break;
+
+ case 'g': /* --group <group>|<gid> */
+ if (sscanf (optarg, "%d", &tid) != 1)
+ {
+ struct group *gr = getgrnam (optarg);
+ if (! gr)
+ eerrorx ("%s: group `%s' not found", progname, optarg);
+ ch_gid = gr->gr_gid;
+ }
+ else
+ ch_gid = tid;
+ break;
+
+ case 'm': /* --make-pidfile */
+ makepidfile = true;
+ break;
+
+ case 'n': /* --name <process-name> */
+ cmd = optarg;
+ break;
+
+ case 'o': /* --oknodo */
+ oknodo = true;
+ break;
+
+ case 'p': /* --pidfile <pid-file> */
+ pidfile = optarg;
+ break;
+
+ case 'q': /* --quiet */
+ quiet = true;
+ break;
+
+ case 's': /* --signal <signal> */
+ sig = parse_signal (optarg);
+ break;
+
+ case 't': /* --test */
+ test = true;
+ break;
+
+ case 'u': /* --user <username>|<uid> */
+ if (sscanf (optarg, "%d", &tid) != 1)
+ {
+ struct passwd *pw = getpwnam (optarg);
+ if (! pw)
+ eerrorx ("%s: user `%s' not found", progname, optarg);
+ uid = pw->pw_uid;
+ }
+ else
+ uid = tid;
+ break;
+
+ case 'r': /* --chroot /new/root */
+ ch_root = optarg;
+ break;
+
+ case 'v': /* --verbose */
+ verbose = true;
+ break;
+
+ case 'x': /* --exec <executable> */
+ exec = optarg;
+ break;
+
+ case '1': /* --stdout /path/to/stdout.lgfile */
+ redirect_stdout = optarg;
+ break;
+
+ case '2': /* --stderr /path/to/stderr.logfile */
+ redirect_stderr = optarg;
+ break;
+
+ default:
+ exit (EXIT_FAILURE);
+ }
+
+ /* Respect RC as well as how we are called */
+ if (rc_is_env ("RC_QUIET", "yes") && ! verbose)
+ quiet = true;
+
+ if (start == stop)
+ eerrorx ("%s: need one of --start or --stop", progname);
+
+ if (start && ! exec)
+ eerrorx ("%s: --start needs --exec", progname);
+
+ if (stop && ! exec && ! pidfile && ! cmd && ! uid)
+ eerrorx ("%s: --stop needs --exec, --pidfile, --name or --user", progname);
+
+ if (makepidfile && ! pidfile)
+ eerrorx ("%s: --make-pidfile is only relevant with --pidfile", progname);
+
+ if (background && ! start)
+ eerrorx ("%s: --background is only relevant with --start", progname);
+
+ if ((redirect_stdout || redirect_stderr) && ! background)
+ eerrorx ("%s: --stdout and --stderr are only relevant with --background",
+ progname);
+
+ argc -= optind;
+ argv += optind;
+
+ /* Validate that the binary rc_exists if we are starting */
+ if (exec && start)
+ {
+ char *tmp;
+ if (ch_root)
+ tmp = rc_strcatpaths (ch_root, exec, NULL);
+ else
+ tmp = exec;
+ if (! rc_is_file (tmp))
+ {
+ eerror ("%s: %s does not exist", progname, 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 && rc_is_file (pidfile))
+ unlink (pidfile);
+
+ if (svcname)
+ rc_set_service_daemon (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", progname, exec);
+
+ if (test)
+ {
+ if (quiet)
+ exit (EXIT_SUCCESS);
+
+ einfon ("Would start %s", exec);
+ while (argc-- > 0)
+ printf("%s ", *argv++);
+ printf ("\n");
+ eindent ();
+ if (ch_uid != 0)
+ einfo ("as user %d", ch_uid);
+ if (ch_gid != 0)
+ einfo ("as group %d", ch_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", progname, 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", progname, nicelevel,
+ strerror(errno));
+ }
+
+ if (ch_root && chroot (ch_root) < 0)
+ eerrorx ("%s: chroot `%s': %s", progname, ch_root, strerror (errno));
+
+ if (ch_dir && chdir (ch_dir) < 0)
+ eerrorx ("%s: chdir `%s': %s", progname, ch_dir, strerror (errno));
+
+ if (makepidfile && pidfile)
+ {
+ FILE *fp = fopen (pidfile, "w");
+ if (! fp)
+ eerrorx ("%s: fopen `%s': %s", progname, 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", progname, pam_strerror(pamh, pamr));
+#endif
+
+ if ((ch_gid) && setgid(ch_gid))
+ eerrorx ("%s: unable to set groupid to %d", progname, ch_gid);
+ if (changeuser && ch_gid)
+ if (initgroups (changeuser, ch_gid))
+ eerrorx ("%s: initgroups (%s, %d)", progname, changeuser, ch_gid);
+ if (ch_uid && setuid (ch_uid))
+ eerrorx ("%s: unable to set userid to %d", progname, ch_uid);
+ else
+ {
+ struct passwd *passwd = getpwuid (ch_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 (env && strncmp (env, "RC_", 3) != 0)
+ {
+ /* For the path character, remove the rcscript bin dir from it */
+ if (strncmp (env, "PATH=" RC_LIBDIR "bin:",
+ strlen ("PATH=" RC_LIBDIR "bin:")) == 0)
+ {
+ char *path = env;
+ char *newpath;
+ int len;
+ path += strlen ("PATH=" RC_LIBDIR "bin:");
+ len = sizeof (char *) * strlen (path) + 6;
+ newpath = rc_xmalloc (len);
+ snprintf (newpath, len, "PATH=%s", path);
+ newenv = rc_strlist_add (newenv, newpath);
+ free (newpath);
+ }
+ else
+ newenv = 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",
+ progname, 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",
+ progname, redirect_stderr, strerror (errno));
+ }
+
+ dup2 (devnull_fd, STDIN_FILENO);
+ if (background)
+ {
+ 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", progname, 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 started `%s'", progname, 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 timeval stopat;
+ struct timeval now;
+
+ if (gettimeofday (&stopat, NULL) != 0)
+ eerrorx ("%s: gettimeofday: %s", progname, strerror (errno));
+
+ stopat.tv_usec += START_WAIT;
+ while (1)
+ {
+ bool alive = false;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = POLL_INTERVAL;
+ if (select (0, 0, 0, 0, &tv) < 0)
+ {
+ /* Let our signal handler handle the interupt */
+ if (errno != EINTR)
+ eerrorx ("%s: select: %s", progname, strerror (errno));
+ }
+
+ if (gettimeofday (&now, NULL) != 0)
+ eerrorx ("%s: gettimeofday: %s", progname, strerror (errno));
+
+ /* 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 && rc_exists (pidfile))
+ {
+ if (do_stop (NULL, NULL, pidfile, uid, 0, true, false, true) > 0)
+ alive = true;
+ }
+ else
+ {
+ if (do_stop (exec, cmd, NULL, uid, 0, true, false, true) > 0)
+ alive = true;
+ }
+ }
+
+ if (! alive)
+ eerrorx ("%s: %s died", progname, exec);
+
+ if (timercmp (&now, &stopat, >))
+ break;
+ }
+ }
+
+ if (svcname)
+ rc_set_service_daemon (svcname, exec, cmd, pidfile, true);
+
+ exit (EXIT_SUCCESS);
+}
diff --git a/src/start-stop-daemon.pam b/src/start-stop-daemon.pam
new file mode 100644
index 0000000..860a3d5
--- /dev/null
+++ b/src/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/strlist.h b/src/strlist.h
new file mode 100644
index 0000000..25bbb4e
--- /dev/null
+++ b/src/strlist.h
@@ -0,0 +1,24 @@
+/*
+ strlist.h
+ String list macros for making char ** arrays
+ Copyright 2007 Gentoo Foundation
+ Based on a previous implementation by Martin Schlemmer
+ Released under the GPLv2
+ */
+
+#ifndef __STRLIST_H__
+#define __STRLIST_H__
+
+/* FIXME: We should replace the macro with an rc_strlist_foreach
+ function, but I'm unsure how to go about this. */
+
+/* Step through each entry in the string list, setting '_pos' to the
+ beginning of the entry. '_counter' is used by the macro as index,
+ but should not be used by code as index (or if really needed, then
+ it should usually by +1 from what you expect, and should only be
+ used in the scope of the macro) */
+#define STRLIST_FOREACH(_list, _pos, _counter) \
+ if ((_list) && _list[0] && ((_counter = 0) == 0)) \
+ while ((_pos = _list[_counter++]))
+
+#endif /* __STRLIST_H__ */