diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile | 153 | ||||
-rw-r--r-- | src/einfo.h | 83 | ||||
-rw-r--r-- | src/env-update.c | 247 | ||||
-rw-r--r-- | src/env_whitelist | 48 | ||||
-rw-r--r-- | src/fstabinfo.c | 146 | ||||
-rw-r--r-- | src/libeinfo.c | 877 | ||||
-rw-r--r-- | src/librc-daemon.c | 600 | ||||
-rw-r--r-- | src/librc-depend.c | 838 | ||||
-rw-r--r-- | src/librc-misc.c | 750 | ||||
-rw-r--r-- | src/librc-strlist.c | 141 | ||||
-rw-r--r-- | src/librc.c | 773 | ||||
-rw-r--r-- | src/mountinfo.c | 246 | ||||
-rw-r--r-- | src/rc-depend.c | 120 | ||||
-rw-r--r-- | src/rc-misc.h | 34 | ||||
-rw-r--r-- | src/rc-plugin.c | 119 | ||||
-rw-r--r-- | src/rc-plugin.h | 15 | ||||
-rw-r--r-- | src/rc-status.c | 142 | ||||
-rw-r--r-- | src/rc-update.c | 162 | ||||
-rw-r--r-- | src/rc.c | 1174 | ||||
-rw-r--r-- | src/rc.h | 180 | ||||
-rw-r--r-- | src/runscript.c | 1097 | ||||
-rw-r--r-- | src/splash.c | 130 | ||||
-rw-r--r-- | src/start-stop-daemon.c | 1047 | ||||
-rw-r--r-- | src/start-stop-daemon.pam | 6 | ||||
-rw-r--r-- | src/strlist.h | 24 |
25 files changed, 9152 insertions, 0 deletions
diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 00000000..131b4d5c --- /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 00000000..7ab52afd --- /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 00000000..c04974b1 --- /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 00000000..ca21935b --- /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 00000000..6f45cc70 --- /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 00000000..e0faf988 --- /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 00000000..02d0d937 --- /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 00000000..0da93aa5 --- /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 00000000..604c5518 --- /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 00000000..981f654b --- /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 00000000..d9c4a539 --- /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 00000000..1fc84420 --- /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 00000000..9d0b3af8 --- /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 00000000..5a4aa55f --- /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 00000000..c02b6a81 --- /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 00000000..b4391ad0 --- /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 00000000..5f2f9b1d --- /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 00000000..8d50a4af --- /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 00000000..b9b4c82e --- /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 00000000..3ba7cc56 --- /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 00000000..bca1195b --- /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 00000000..3a4edfd9 --- /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 00000000..e5dae783 --- /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 00000000..860a3d52 --- /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 00000000..25bbb4e0 --- /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__ */ |