diff options
author | Roy Marples <roy@marples.name> | 2007-04-05 11:18:42 +0000 |
---|---|---|
committer | Roy Marples <roy@marples.name> | 2007-04-05 11:18:42 +0000 |
commit | 5af58b45146ab5253ca964738f4e45287bf963d4 (patch) | |
tree | 68d3a9a61fa55dd7fe273db776c375f797edaa5b /src |
Rewrite the core parts in C. We now provide librc so other programs can
query runlevels, services and state without using bash. We also provide
libeinfo so other programs can easily use our informational functions.
As such, we have dropped the requirement of using bash as the init script
shell. We now use /bin/sh and have strived to make the scripts as portable
as possible. Shells that work are bash and dash. busybox works provided
you disable s-s-d. If you have WIPE_TMP set to yes in conf.d/bootmisc you
should disable find too.
zsh and ksh do not work at this time.
Networking support is currently being re-vamped also as it was heavily bash
array based. As such, a new config format is available like so
config_eth0="1.2.3.4/24 5.6.7.8/16"
or like so
config_eth0="'1.2.3.4 netmask 255.255.255.0' '5.6.7.8 netmask 255.255.0.0'"
We will still support the old bash array format provided that /bin/sh IS
a link it bash.
ChangeLog for baselayout-1 can be found in our SVN repo.
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__ */ |