diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Makefile | 153 | ||||
| -rw-r--r-- | src/einfo.h | 83 | ||||
| -rw-r--r-- | src/env-update.c | 247 | ||||
| -rw-r--r-- | src/env_whitelist | 48 | ||||
| -rw-r--r-- | src/fstabinfo.c | 146 | ||||
| -rw-r--r-- | src/libeinfo.c | 877 | ||||
| -rw-r--r-- | src/librc-daemon.c | 600 | ||||
| -rw-r--r-- | src/librc-depend.c | 838 | ||||
| -rw-r--r-- | src/librc-misc.c | 750 | ||||
| -rw-r--r-- | src/librc-strlist.c | 141 | ||||
| -rw-r--r-- | src/librc.c | 773 | ||||
| -rw-r--r-- | src/mountinfo.c | 246 | ||||
| -rw-r--r-- | src/rc-depend.c | 120 | ||||
| -rw-r--r-- | src/rc-misc.h | 34 | ||||
| -rw-r--r-- | src/rc-plugin.c | 119 | ||||
| -rw-r--r-- | src/rc-plugin.h | 15 | ||||
| -rw-r--r-- | src/rc-status.c | 142 | ||||
| -rw-r--r-- | src/rc-update.c | 162 | ||||
| -rw-r--r-- | src/rc.c | 1174 | ||||
| -rw-r--r-- | src/rc.h | 180 | ||||
| -rw-r--r-- | src/runscript.c | 1097 | ||||
| -rw-r--r-- | src/splash.c | 130 | ||||
| -rw-r--r-- | src/start-stop-daemon.c | 1047 | ||||
| -rw-r--r-- | src/start-stop-daemon.pam | 6 | ||||
| -rw-r--r-- | src/strlist.h | 24 | 
25 files changed, 9152 insertions, 0 deletions
| diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 00000000..131b4d5c --- /dev/null +++ b/src/Makefile @@ -0,0 +1,153 @@ +# Copyright 1999-2007 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +CC ?= gcc + +CFLAGS ?= -Wall -O2 -pipe +CFLAGS += -pedantic -std=c99 \ +    -Wall -Wextra -Wunused -Wimplicit -Wshadow -Wformat=2 \ +    -Wmissing-declarations -Wno-missing-prototypes -Wwrite-strings \ +    -Wbad-function-cast -Wnested-externs -Wcomment -Winline \ +    -Wchar-subscripts -Wcast-align -Wno-format-nonliteral + +# Early GCC versions don't support these flags, so you may need to comment +# this line out +CFLAGS += -Wsequence-point -Wextra -Wdeclaration-after-statement + +# For debugging. -Werror is pointless due to ISO C issues with dlsym +#CFLAGS += -ggdb + +DESTDIR = +LIB = lib + +LIBEINFOSOVER = 0 +LIBEINFOSO = libeinfo.so.$(LIBRCSOVER) +LIBEINFOOBJS= libeinfo.o + +LIBRCSOVER = 0 +LIBRCSO = librc.so.$(LIBRCSOVER) +LIBRCOBJS= librc.o librc-depend.o librc-daemon.o librc-misc.o librc-strlist.o + +LIB_TARGETS = $(LIBEINFOSO) $(LIBRCSO) +BIN_TARGETS = rc-status +SBIN_TARGETS = env-update fstabinfo mountinfo \ +	       rc rc-depend rc-update runscript start-stop-daemon +SYS_WHITELIST = env_whitelist + +TARGET = $(LIB_TARGETS) $(BIN_TARGETS) $(SBIN_TARGETS) + +RCLINKS = einfon einfo ewarnn ewarn eerrorn eerror ebegin eend ewend \ +	      eindent eoutdent eflush color_terminal \ +	      veinfo vewarn vebegin veend vewend veindent veoutdent \ +	      service_starting service_inactive service_started \ +	      service_stopping service_stopped \ +	      service_inactive service_wasinactive \ +	      service_coldplugged \ +	      mark_service_starting mark_service_inactive mark_service_started \ +	      mark_service_stopping mark_service_stopped \ +	      mark_service_inactive mark_service_wasinactive \ +	      mark_service_coldplugged \ +	      get_options save_options \ +	      is_runlevel_start is_runlevel_stop service_started_daemon + +# Quick hack to make my life easier on BSD and Linux +ifeq ($(OS),) +OS=$(shell uname -s) +ifneq ($(OS),Linux) +OS=BSD +endif +endif + +ifeq ($(OS),Linux) +LDLIBS_RC = -ldl +LDLIBS_RS = -ldl +# Shouldn't need this, but it's the easiest workaround for silly +# Linux headers that don't work with -std=c99 +override CFLAGS += -D_GNU_SOURCE +endif +ifeq ($(OS),BSD) +override LDLIBS += -lkvm +endif + +HAVE_PAM =  +ifdef HAVE_PAM +CFLAGS_SSD = -DHAVE_PAM +LDLIBS_SSD = -lpam +endif + +# We also define _BSD_SOURCE so both Linux and the BSDs get a few +# handy functions which makes our lives a lot easier +override CFLAGS += -DLIBDIR=\"$(LIB)\" + +# IMPORTANT!!! +# Remove this when releasing as it's a security risk +# However, this does save us using libtool when we're testing +# NOTE: The toplevel Makefile for baselayout will automatically +# disable then when doing `make dist` +##override LDFLAGS += -Wl,-rpath . + +all: $(TARGET) + +$(LIBEINFOOBJS): CFLAGS += -fPIC +$(LIBEINFOSO): LDLIBS = +$(LIBEINFOSO): $(LIBEINFOOBJS)  +	$(CC) -fPIC -shared -Wl,-soname,$(LIBEINFOSO) -o $(LIBEINFOSO) $(LIBEINFOOBJS) +	ln -sf $(LIBEINFOSO) libeinfo.so + +$(LIBRCOBJS): CFLAGS += -fPIC +$(LIBRCSO): $(LIBRCOBJS)  +	$(CC) -fPIC -shared -Wl,-soname,$(LIBRCSO) -o $(LIBRCSO) $(LIBRCOBJS) +	ln -sf $(LIBRCSO) librc.so + +splash: CFLAGS += -fPIC +splash: splash.o +	$(CC) -fPIC -shared -Wl,-soname,splash.so -o splash.so splash.o + +env-update: $(LIBEINFOSO) $(LIBRCSO) env-update.o + +fstabinfo: $(LIBEINFOSO) fstabinfo.o + +mountinfo: $(LIBEINFOSO) $(LIBRCSO) mountinfo.o + +rc-depend: $(LIBEINFOSO) $(LIBRCSO) rc-depend.o + +rc-status: $(LIBEINFOSO) $(LIBRCSO) rc-status.o + +rc-update: $(LIBEINFOSO) $(LIBRCSO) rc-update.o + +rc: LDLIBS += $(LDLIBS_RC) +rc: $(LIBEINFOSO) $(LIBRCSO) rc-plugin.o rc.o + +runscript: LDLIBS += $(LDLIBS_RS) +runscript: $(LIBEINFOSO) $(LIBRCSO) rc-plugin.o runscript.o + +start-stop-daemon: CFLAGS += $(CFLAGS_SSD) +start-stop-daemon: LDLIBS += $(LDLIBS_SSD) +start-stop-daemon: $(LIBEINFOSO) $(LIBRCSO) start-stop-daemon.o + +links: rc +	for x in $(RCLINKS) $(RCPRIVLINKS); do ln -sf rc $$x; done + +install: $(TARGET) +	install -m 0755 -d $(DESTDIR)/$(LIB) +	install -m 0755 $(LIB_TARGETS) $(DESTDIR)/$(LIB) +	ln -sf $(LIBEINFOSO) $(DESTDIR)/$(LIB)/libeinfo.so +	ln -sf $(LIBRCSO) $(DESTDIR)/$(LIB)/librc.so +	install -m 0755 -d $(DESTDIR)/usr/include +	install -m 0644 einfo.h rc.h $(DESTDIR)/usr/include +	install -m 0755 -d $(DESTDIR)/bin +	install -m 0755 $(BIN_TARGETS) $(DESTDIR)/bin +	install -m 0755 -d $(DESTDIR)/sbin +	install -m 0755 $(SBIN_TARGETS) $(DESTDIR)/sbin +	install -m 0755 -d $(DESTDIR)/$(LIB)/rcscripts/conf.d +	install -m 0644 $(SYS_WHITELIST) $(DESTDIR)/$(LIB)/rcscripts/conf.d +	install -m 0755 -d $(DESTDIR)/$(LIB)/rcscripts/bin +	for x in $(RCLINKS); do ln -sf $(DESTDIR)/sbin/rc $(DESTDIR)/$(LIB)/rcscripts/bin/$$x; done +	if test "$(HAVE_PAM)" != "" ; then \ +		install -m 0755 -d $(DESTDIR)/etc/pam.d ; \ +		install -m 0644 start-stop-daemon.pam $(DESTDIR)/etc/pam.d/start-stop-daemon ; \ +	fi + +clean: +	rm -f $(TARGET) $(RCLINKS) $(RCPRIVLINKS) +	rm -f *.o *~ *.core *.so diff --git a/src/einfo.h b/src/einfo.h new file mode 100644 index 00000000..7ab52afd --- /dev/null +++ b/src/einfo.h @@ -0,0 +1,83 @@ +/* +   rc.h  +   Header file for external applications to get RC information. +   Copyright 2007 Gentoo Foundation +   Released under the GPLv2 +   */ + +#ifndef __EINFO_H__ +#define __EINFO_H__ + +#ifdef __GNUC__ +#  define EINFO_PRINTF(_one, _two)  __attribute__ ((__format__ (__printf__, _one, _two))) +#  define EINFO_XPRINTF(_one, _two)  __attribute__ ((__noreturn__, __format__ (__printf__, _one, _two))) +#endif + +#include <sys/types.h> +#include <stdbool.h> + +typedef enum +{ +  einfo_good, +  einfo_warn, +  einfo_bad, +  einfo_hilite, +  einfo_bracket, +  einfo_normal +} einfo_color_t; + +/* Colour codes used by the below functions. */ +#define EINFO_GOOD	"\033[32;01m" +#define EINFO_WARN	"\033[33;01m" +#define EINFO_BAD	"\033[31;01m" +#define EINFO_HILITE	"\033[36;01m" +#define EINFO_BRACKET	"\033[34;01m" +#define EINFO_NORMAL	"\033[0m" + +/* Handy macros to easily use the above in a custom manner */ +#define PEINFO_GOOD	if (colour_terminal ()) printf (EINFO_GOOD) +#define PEINFO_WARN	if (colour_terminal ()) printf (EINFO_WARN) +#define PEINFO_BAD	if (colour_terminal ()) printf (EINFO_BAD) +#define PEINFO_HILITE	if (colour_terminal ()) printf (EINFO_HILITE) +#define PEINFO_BRACKET	if (colour_terminal ()) printf (EINFO_BRACKET) +#define PEINFO_NORMAL	if (colour_terminal ()) printf (EINFO_NORMAL) + +/* We work out if the terminal supports colour or not through the use +   of the TERM env var. We cache the reslt in a static bool, so +   subsequent calls are very fast. */ +/* The n suffix means that a newline is NOT appended to the string +   The v prefix means that we only print it when RC_VERBOSE=yes */ +bool colour_terminal (void); +int einfon (const char *fmt, ...) EINFO_PRINTF (1, 2); +int ewarnn (const char *fmt, ...) EINFO_PRINTF (1, 2); +int eerrorn (const char *fmt, ...) EINFO_PRINTF (1, 2); +int einfo (const char *fmt, ...) EINFO_PRINTF(1, 2); +int ewarn (const char *fmt, ...) EINFO_PRINTF (1, 2); +void ewarnx (const char *fmt, ...) EINFO_XPRINTF (1,2); +int eerror (const char *fmt, ...) EINFO_PRINTF (1,2); +void eerrorx (const char *fmt, ...) EINFO_XPRINTF (1,2); +int ebegin (const char *fmt, ...) EINFO_PRINTF (1, 2); +int eend (int retval, const char *fmt, ...) EINFO_PRINTF (2, 3); +int ewend (int retval, const char *fmt, ...) EINFO_PRINTF (2, 3); +void ebracket (int col, einfo_color_t color, const char *msg); +void eindent (void); +void eoutdent (void); + +int veinfon (const char *fmt, ...) EINFO_PRINTF (1, 2); +int vewarnn (const char *fmt, ...) EINFO_PRINTF (1, 2); +int vebeginn (const char *fmt, ...) EINFO_PRINTF (1, 2); +int veendn (int retval, const char *fmt, ...) EINFO_PRINTF (2, 3); +int vewendn (int retval, const char *fmt, ...) EINFO_PRINTF (2, 3); +int veinfo (const char *fmt, ...) EINFO_PRINTF (1, 2); +int vewarn (const char *fmt, ...) EINFO_PRINTF (1, 2); +int vebegin (const char *fmt, ...) EINFO_PRINTF (1, 2); +int veend (int retval, const char *fmt, ...) EINFO_PRINTF (2, 3); +int vewend (int retval, const char *fmt, ...) EINFO_PRINTF (2, 3); +void veindent (void); +void veoutdent (void); + +/* If RC_EBUFFER is set, then we buffer all the above commands. +   As such, we need to flush the buffer when done. */ +void eflush(void); + +#endif diff --git a/src/env-update.c b/src/env-update.c new file mode 100644 index 00000000..c04974b1 --- /dev/null +++ b/src/env-update.c @@ -0,0 +1,247 @@ +/* +   env-update +   Create /etc/profile.env (sh), /etc/csh.env from /etc/env.d +   Run ldconfig as required + +   Copyright 2007 Gentoo Foundation +   Released under the GPLv2 +   */ + +#include <errno.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "einfo.h" +#include "rc.h" +#include "rc-misc.h" +#include "strlist.h" + +#define ENVDIR		"/etc/env.d" +#define PROFILE_ENV	"/etc/profile.env" +#define CSH_ENV		"/etc/csh.env" +#define LDSOCONF	"/etc/ld.so.conf" + +#define NOTICE	"# THIS FILE IS AUTOMATICALLY GENERATED BY env-update.\n" \ +		"# DO NOT EDIT THIS FILE. CHANGES TO STARTUP PROFILES\n" \ +		"# GO INTO %s NOT %s\n\n" + +#define LDNOTICE	"# ld.so.conf autogenerated by env-update; make all\n" \ +			"# changes to contents of /etc/env.d directory\n" + +static const char *specials[] = +{ +  "ADA_INCLUDE_PATH", +  "ADA_OBJECTS_PATH", +  "CLASSPATH", +  "INFOPATH", +  "KDEDIRS", +  "LDPATH", +  "MANPATH", +  "PATH", +  "PKG_CONFIG_PATH", +  "PRELINK_PATH", +  "PRELINK_PATH_MASK", +  "PYTHONPATH", +  "ROOTPATH", +  NULL +}; + +static const char *special_spaces[] =  +{ +  "CONFIG_PROTECT", +  "CONFIG_PROTECT_MASK", +  NULL, +}; + +static char *applet = NULL; + +int main (int argc, char **argv) +{ +  char **files = rc_ls_dir (NULL, ENVDIR, 0); +  char *file; +  char **envs = NULL; +  char *env; +  int i = 0; +  FILE *fp; +  bool ld = true; +  char *ldent; +  char **ldents = NULL; +  int nents = 0; + +  applet = argv[0]; + +  if (! files) +    eerrorx ("%s: no files in " ENVDIR " to process", applet); + +  STRLIST_FOREACH (files, file, i) +    { +      char *path = rc_strcatpaths (ENVDIR, file, NULL); +      char **entries = NULL; +      char *entry; +      int j; + +      if (! rc_is_dir (path)) +	entries = rc_get_config (NULL, path); +      free (path); + +      STRLIST_FOREACH (entries, entry, j) +	{ +	  char *tmpent = rc_xstrdup (entry); +	  char *value = tmpent; +	  char *var = strsep (&value, "="); +	  int k; +	  bool isspecial = false; +	  bool isspecial_spaced = false; +	  bool replaced = false; + +	  for (k = 0; special_spaces[k]; k++) +	   if (strcmp (special_spaces[k], var) == 0) +	     { +	       isspecial = true; +	       isspecial_spaced = true; +	       break; +	     } + +	  if (! isspecial) +	    { +	      for (k = 0; specials[k]; k++) +		if (strcmp (specials[k], var) == 0) +		  { +		    isspecial = true; +		    break; +		  } +	    } +	    +	  /* Skip blank vars */ +	  if (isspecial && +	      (! value || strlen (value)) == 0) +	    { +	      free (tmpent); +	      continue; +	    } +  +	  STRLIST_FOREACH (envs, env, k) +	    { +	      char *tmpenv = rc_xstrdup (env); +	      char *tmpvalue = tmpenv; +	      char *tmpentry = strsep (&tmpvalue, "="); + +	      if (strcmp (tmpentry, var) == 0) +		{ +		  if (isspecial) +		    { +		      envs[k - 1] = rc_xrealloc (envs[k - 1], +						 strlen (envs[k - 1]) + +						 strlen (entry) + 1); +		      sprintf (envs[k - 1] + strlen (envs[k - 1]), +			       "%s%s", isspecial_spaced ? " " : ":", value); +		    } +		  else +		    { +		      free (envs[k - 1]); +		      envs[k - 1] = strdup (entry); +		    } +		  replaced = true; +		} +	      free (tmpenv); + +	      if (replaced) +		break; +	    } + +	  if (! replaced) +	    envs = rc_strlist_addsort (envs, entry); + +	  free (tmpent); +	} +    } + +  if ((fp = fopen (PROFILE_ENV, "w")) == NULL) +    eerrorx ("%s: fopen `%s': %s", applet, PROFILE_ENV, strerror (errno)); +  fprintf (fp, NOTICE, "/etc/profile", PROFILE_ENV); +  STRLIST_FOREACH (envs, env, i) +    { +      char *tmpent = rc_xstrdup (env); +      char *value = tmpent; +      char *var = strsep (&value, "="); +      if (strcmp (var, "LDPATH") != 0) +	fprintf (fp, "export %s='%s'\n", var, value); +      free (tmpent); +    } +  fclose (fp); + +  if ((fp = fopen (CSH_ENV, "w")) == NULL) +    eerrorx ("%s: fopen `%s': %s", applet, PROFILE_ENV, strerror (errno)); +  fprintf (fp, NOTICE, "/etc/csh.cshrc", PROFILE_ENV); +  STRLIST_FOREACH (envs, env, i) +    { +      char *tmpent = rc_xstrdup (env); +      char *value = tmpent; +      char *var = strsep (&value, "="); +      if (strcmp (var, "LDPATH") != 0) +	fprintf (fp, "setenv %s '%s'\n", var, value); +      free (tmpent); +    } +  fclose (fp); + +  ldent = rc_get_config_entry (envs, "LDPATH"); + +   if (! ldent || +       (argc > 1 && argv[1] && strcmp (argv[1], "--no-ldconfig") == 0)) +     { +       free (envs); +       return (EXIT_SUCCESS); +     } + +  while ((file = strsep (&ldent, ":"))) +    { +      if (strlen (file) == 0) +	continue; + +      ldents = rc_strlist_add (ldents, file); +      nents++; +    } + +  /* Update ld.so.conf only if different */ +  if (rc_exists (LDSOCONF)) +    { +      char **lines = rc_get_list (NULL, LDSOCONF); +      char *line; +      ld = false; +      STRLIST_FOREACH (lines, line, i) +       if (i > nents || strcmp (line, ldents[i - 1]) != 0) +	 { +	   ld = true; +	   break; +	 } +      if (i - 1 != nents) +	ld = true; +    } + +  if (ld) +    { +      int retval = 0; + +      if ((fp = fopen (LDSOCONF, "w")) == NULL) +	eerrorx ("%s: fopen `%s': %s", applet, LDSOCONF, strerror (errno)); +      fprintf (fp, LDNOTICE); +      STRLIST_FOREACH (ldents, ldent, i) +       fprintf (fp, "%s\n", ldent); +      fclose (fp); + +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) +      ebegin ("Regenerating /var/run/ld-elf.so.hints"); +      retval = system ("/sbin/ldconfig -elf -i '" LDSOCONF "'"); +#else +      ebegin ("Regenerating /etc/ld.so.cache"); +      retval = system ("/sbin/ldconfig"); +#endif +      eend (retval, NULL); +    } + +  return(EXIT_SUCCESS); +} diff --git a/src/env_whitelist b/src/env_whitelist new file mode 100644 index 00000000..ca21935b --- /dev/null +++ b/src/env_whitelist @@ -0,0 +1,48 @@ +# System environment whitelist for rc-system +# See /etc/conf.d/env_whitelist for details. + +# +# Internal variables needed for operation of rc-system +# NB: Do not modify below this line if you do not know what you are doing!! +# + +# Hotplug +IN_HOTPLUG + +# RC network script support +IN_BACKGROUND +RC_INTERFACE_KEEP_CONFIG + +# Default shell stuff +PATH +SHELL +USER +HOME +TERM + +# Language variables +LANG +LC_CTYPE +LC_NUMERIC +LC_TIME +LC_COLLATE +LC_MONETARY +LC_MESSAGES +LC_PAPER +LC_NAME +LC_ADDRESS +LC_TELEPHONE +LC_MEASUREMENT +LC_IDENTIFICATION +LC_ALL + +# From /sbin/init +INIT_HALT +INIT_VERSION +RUNLEVEL +PREVLEVEL +CONSOLE + +# Allow this through too so we can prefer stuff in /lib when shutting down +# or going to single mode. +LD_LIBRARY_PATH diff --git a/src/fstabinfo.c b/src/fstabinfo.c new file mode 100644 index 00000000..6f45cc70 --- /dev/null +++ b/src/fstabinfo.c @@ -0,0 +1,146 @@ +/* +   fstabinfo.c +   Gets information about /etc/fstab. + +   Copyright 2007 Gentoo Foundation +   */ + +#include <errno.h> +#include <libgen.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* Yay for linux and it's non liking of POSIX functions. +   Okay, we could use getfsent but the man page says use getmntent instead +   AND we don't have getfsent on uclibc or dietlibc for some odd reason. */ +#ifdef __linux__ +#define HAVE_GETMNTENT +#include <mntent.h> +#define GET_ENT getmntent (fp) +#define GET_ENT_FILE(_name) getmntfile (fp, _name) +#define END_ENT endmntent (fp) +#define ENT_DEVICE(_ent) ent->mnt_fsname +#define ENT_FILE(_ent) ent->mnt_dir +#define ENT_TYPE(_ent) ent->mnt_type +#define ENT_OPTS(_ent) ent->mnt_opts +#define ENT_PASS(_ent) ent->mnt_passno +#else +#define HAVE_GETFSENT +#include <fstab.h> +#define GET_ENT getfsent () +#define GET_ENT_FILE(_name) getfsfile (_name) +#define END_ENT endfsent () +#define ENT_DEVICE(_ent) ent->fs_spec +#define ENT_TYPE(_ent) ent->fs_vfstype +#define ENT_FILE(_ent) ent->fs_file +#define ENT_OPTS(_ent) ent->fs_mntops +#define ENT_PASS(_ent) ent->fs_passno +#endif + +#include "einfo.h" + +#ifdef HAVE_GETMNTENT +static struct mntent *getmntfile (FILE *fp, const char *file) +{ +  struct mntent *ent; + +  while ((ent = getmntent (fp))) +   if (strcmp (file, ent->mnt_dir) == 0) +     return (ent); + +  return (NULL); +} +#endif + +int main (int argc, char **argv) +{ +  int i; +#ifdef HAVE_GETMNTENT +  FILE *fp; +  struct mntent *ent; +#else +  struct fstab *ent; +#endif +  int result = EXIT_FAILURE; +  char *p; +  char *token; +  int n = 0; + +  for (i = 1; i < argc; i++) +    { +#ifdef HAVE_GETMNTENT +      fp = setmntent ("/etc/fstab", "r"); +#endif + +      if (strcmp (argv[i], "--fstype") == 0 && i + 1 < argc) +	{ +	  i++; +	  p = argv[i]; +	  while ((token = strsep (&p, ","))) +	    while ((ent = GET_ENT)) +	     if (strcmp (token, ENT_TYPE (ent)) == 0) +	       printf ("%s\n", ENT_FILE (ent)); +	  result = EXIT_SUCCESS; +	} + +      if (strcmp (argv[i], "--mount-cmd") == 0 && i + 1 < argc) +	{ +	  i++; +	  if ((ent = GET_ENT_FILE (argv[i])) == NULL) +	    continue; +	  printf ("-o %s -t %s %s %s\n", ENT_OPTS (ent), ENT_TYPE (ent), +		  ENT_DEVICE (ent), ENT_FILE (ent)); +	  result = EXIT_SUCCESS; +	} + +      if (strcmp (argv[i], "--opts") == 0 && i + 1 < argc) +	{ +	  i++; +	  if ((ent = GET_ENT_FILE (argv[i])) == NULL) +	    continue; +	  printf ("%s\n", ENT_OPTS (ent)); +	  result = EXIT_SUCCESS; +	} +	   +      if (strcmp (argv[i], "--passno") == 0 && i + 1 < argc) +	{ +	  i++; +	  switch (argv[i][0]) +	    { +	    case '=': +	    case '<': +	    case '>': +	      if (sscanf (argv[i] + 1, "%d", &n) != 1) +		eerrorx ("%s: invalid passno %s", argv[0], argv[i] + 1); + +	      while ((ent = GET_ENT)) +		{ +		  if (((argv[i][0] == '=' && n == ENT_PASS (ent)) || +		       (argv[i][0] == '<' && n > ENT_PASS (ent)) || +		       (argv[i][0] == '>' && n < ENT_PASS (ent))) && +		      strcmp (ENT_FILE (ent), "none") != 0) +		    printf ("%s\n", ENT_FILE (ent)); +		} + +	    default: +	      if ((ent = GET_ENT_FILE (argv[i])) == NULL) +		continue; +	      printf ("%d\n", ENT_PASS (ent)); +	      result = EXIT_SUCCESS; +	    } +	} + +      END_ENT; + +      if (result != EXIT_SUCCESS) +	{ +	  eerror ("%s: unknown option `%s'", basename (argv[0]), argv[i]); +	  break; +	} +  +    } + +  exit (result); +} + diff --git a/src/libeinfo.c b/src/libeinfo.c new file mode 100644 index 00000000..e0faf988 --- /dev/null +++ b/src/libeinfo.c @@ -0,0 +1,877 @@ +/* +   einfo.c +   Gentoo informational functions +   Copyright 2007 Gentoo Foundation +   */ + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <errno.h> +#include <limits.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "einfo.h" +#include "rc.h" +#include "rc-misc.h" + +/* Incase we cannot work out how many columns from ioctl, supply a default */ +#define DEFAULT_COLS		 80 + +#define OK			"ok" +#define NOT_OK			"!!" + +#define CHECK_VERBOSE		if (! is_env ("RC_VERBOSE", "yes")) return 0 + +/* Number of spaces for an indent */ +#define INDENT_WIDTH		2 + +/* How wide can the indent go? */ +#define INDENT_MAX		40 + +#define EBUFFER_LOCK		RC_SVCDIR "ebuffer/.lock" + +/* A cheat sheet of colour capable terminals +   This is taken from DIR_COLORS from GNU coreutils +   We embed it here as we shouldn't depend on coreutils */ +static const char *colour_terms[] = +{ +  "Eterm", +  "ansi", +  "color-xterm", +  "con132x25", +  "con132x30", +  "con132x43", +  "con132x60", +  "con80x25", +  "con80x28", +  "con80x30", +  "con80x43", +  "con80x50", +  "con80x60", +  "cons25", +  "console", +  "cygwin", +  "dtterm", +  "gnome", +  "konsole", +  "kterm", +  "linux", +  "linux-c", +  "mach-color", +  "mlterm", +  "putty", +  "rxvt", +  "rxvt-cygwin", +  "rxvt-cygwin-native", +  "rxvt-unicode", +  "screen", +  "screen-bce", +  "screen-w", +  "screen.linux", +  "vt100", +  "xterm", +  "xterm-256color", +  "xterm-color", +  "xterm-debian", +  NULL +}; + +static bool is_env (const char *var, const char *val) +{ +  char *v; + +  if (! var) +    return (false); + +  v = getenv (var); +  if (! v) +    return (val == NULL ? true : false); + +  return (strcasecmp (v, val) == 0 ? true : false); +} + +bool colour_terminal (void) +{ +  static int in_colour = -1; +  int i = 0; +  char *term; + +  if (in_colour == 0) +    return (false); +  if (in_colour == 1) +    return (true); + +  term = getenv ("TERM"); +  /* If $TERM isn't set then the chances are we're in single user mode */ +  if (! term) +    return (true); + +  while (colour_terms[i]) +    { +      if (strcmp (colour_terms[i], term) == 0) +	{ +	  in_colour = 1; +	  return (true); +	} +      i++; +    } + +  in_colour = 0; +  return (false); +} + +static int get_term_columns (void) +{ +#ifdef TIOCGSIZE /* BSD */ +  struct ttysize ts; + +  if (ioctl(0, TIOCGSIZE, &ts) == 0) +    return (ts.ts_cols); +#elif TIOCGWINSZ /* Linux */ +  struct winsize ws;  + +  if (ioctl(0, TIOCGWINSZ, &ws) == 0) +    return (ws.ws_col); +#endif + +  return (DEFAULT_COLS); +} + +static int ebuffer (const char *cmd, int retval, const char *fmt, va_list ap) +{ +  char *file = getenv ("RC_EBUFFER"); +  FILE *fp; +  char buffer[RC_LINEBUFFER]; +  int l = 1; + +  if (! file || ! cmd || strlen (cmd) < 4) +    return (0); + +  if (! (fp = fopen (file, "a"))) +    { +      fprintf (stderr, "fopen `%s': %s\n", file, strerror (errno)); +      return (0); +    } + +  fprintf (fp, "%s %d ", cmd, retval); + +  if (fmt) +    { +      l = vsnprintf (buffer, sizeof (buffer), fmt, ap); +      fprintf (fp, "%d %s\n", l, buffer); +    } +  else +    fprintf (fp, "0\n"); + +  fclose (fp); +  return (l); +} + +typedef struct func +{ +  const char *name; +  int (*efunc) (const char *fmt, ...); +  int (*eefunc) (int retval, const char *fmt, ...); +  void (*eind) (void); +} func_t; + +static const func_t funcmap[] = { +    { "einfon", &einfon, NULL, NULL }, +    { "ewarnn", &ewarnn, NULL, NULL}, +    { "eerrorn", &eerrorn, NULL, NULL}, +    { "einfo", &einfo, NULL, NULL }, +    { "ewarn", &ewarn, NULL, NULL }, +    { "eerror", &eerror, NULL, NULL }, +    { "ebegin", &ebegin, NULL, NULL }, +    { "eend", NULL, &eend, NULL }, +    { "ewend", NULL, &ewend, NULL }, +    { "eindent", NULL, NULL, &eindent }, +    { "eoutdent", NULL, NULL, &eoutdent }, +    { "veinfon", &veinfon, NULL, NULL }, +    { "vewarnn", &vewarnn, NULL, NULL }, +    { "veinfo", &veinfo, NULL, NULL }, +    { "vewarn", &vewarn, NULL, NULL }, +    { "vebegin", &vebegin, NULL, NULL }, +    { "veend", NULL, &veend, NULL }, +    { "vewend", NULL, &vewend, NULL }, +    { "veindent" ,NULL, NULL, &veindent }, +    { "veoutdent", NULL, NULL, &veoutdent }, +    { NULL, NULL, NULL, NULL }, +}; + +void eflush (void) +{ +  FILE *fp; +  char *file = getenv ("RC_EBUFFER"); +  char buffer[RC_LINEBUFFER]; +  char *cmd; +  int retval = 0; +  int length = 0; +  char *token; +  char *p; +  struct stat buf; +  pid_t pid; +  char newfile[PATH_MAX]; +  int i = 1; + +  if (! file|| (stat (file, &buf) != 0)) +    { +      errno = 0; +      return; +    } + +  /* Find a unique name for our file */ +  while (true) +    { +      snprintf (newfile, sizeof (newfile), "%s.%d", file, i); +      if (stat (newfile, &buf) != 0) +	{ +	  if (rename (file, newfile)) +	    fprintf (stderr, "rename `%s' `%s': %s\n", file, newfile, +		     strerror (errno)); +	  break; +	} +      i++; +    } + +  /* We fork a child process here so we don't hold anything up */ +  if ((pid = fork ()) == -1) +    { +      fprintf (stderr, "fork: %s", strerror (errno)); +      return; +    } + +  if (pid != 0) +    return; + +  /* Spin until we can lock the ebuffer */ +  while (true) +    { +      struct timeval tv; +      tv.tv_sec = 0; +      tv.tv_usec = 20000; +      select (0, NULL, NULL, NULL, &tv); +      errno = 0; +      if (link (newfile, EBUFFER_LOCK) == 0) +	break; +      if (errno != EEXIST) +	fprintf (stderr, "link `%s' `%s': %s\n", newfile, EBUFFER_LOCK, +		 strerror (errno)); +    } + +  if (! (fp = fopen (newfile, "r"))) +    { +      fprintf (stderr, "fopen `%s': %s\n", newfile, strerror (errno)); +      return; +    } + +  unsetenv ("RC_EBUFFER"); + +  memset (buffer, 0, RC_LINEBUFFER); +  while (fgets (buffer, RC_LINEBUFFER, fp)) +    { +      i = strlen (buffer) - 1; +      if (i < 1) +	continue; + +      if (buffer[i] == '\n') +	buffer[i] = 0; + +      p = buffer; +      cmd = strsep (&p, " "); +      token = strsep (&p, " "); +      if (sscanf (token, "%d", &retval) != 1) +	{ +	  fprintf (stderr, "eflush `%s': not a number", token); +	  continue; +	} +      token = strsep (&p, " "); +      if (sscanf (token, "%d", &length) != 1) +	{ +	  fprintf (stderr, "eflush `%s': not a number", token); +	  continue; +	} + +      i = 0; +      while (funcmap[i].name) +	{ +	  if (strcmp (funcmap[i].name, cmd) == 0) +	    { +	      if (funcmap[i].efunc) +		{ +		  if (p) +		    funcmap[i].efunc ("%s", p); +		  else +		    funcmap[i].efunc (NULL, NULL); +		} +	      else if (funcmap[i].eefunc) +		{ +		  if (p) +		    funcmap[i].eefunc (retval, "%s", p); +		  else +		    funcmap[i].eefunc (retval, NULL, NULL); +		} +	      else if (funcmap[i].eind) +		funcmap[i].eind (); +	      else +		fprintf (stderr, "eflush `%s': no function defined\n", cmd); +	      break; +	    } +	  i++; +	} + +      if (! funcmap[i].name) +	fprintf (stderr, "eflush `%s': invalid function\n", cmd); +    } +  fclose (fp); + +  if (unlink (EBUFFER_LOCK)) +    fprintf (stderr, "unlink `%s': %s", EBUFFER_LOCK, strerror (errno)); +   +  if (unlink (newfile)) +    fprintf (stderr, "unlink `%s': %s", newfile, strerror (errno)); + +  _exit (EXIT_SUCCESS); +} + +#define EBUFFER(_cmd, _retval, _fmt, _ap) \ +{ \ +  int _i = ebuffer (_cmd, _retval, _fmt, _ap); \ +  if (_i) \ +    return (_i); \ +} + +static void elog (int level, const char *fmt, va_list ap) +{ +  char *e = getenv ("RC_ELOG"); + +  if (e) +    { +      closelog (); +      openlog (e, LOG_PID, LOG_DAEMON); +      vsyslog (level, fmt, ap); +    } +} +static int _eindent (FILE *stream) +{ +  char *env = getenv ("RC_EINDENT"); +  int amount = 0; +  char indent[INDENT_MAX]; + +  if (env) +    { +      errno = 0; +      amount = strtol (env, NULL, 0); +      if (errno != 0 || amount < 0) +	amount = 0; +      else if (amount > INDENT_MAX) +	amount = INDENT_MAX; + +      if (amount > 0) +	memset (indent, ' ', amount); +    } + +  /* Terminate it */ +  memset (indent + amount, 0, 1); + +  return (fprintf (stream, "%s", indent)); +} + +#define VEINFON(_file, _colour) \ +  if (colour_terminal ()) \ +    fprintf (_file, " " _colour "*" EINFO_NORMAL " "); \ +  else \ +    fprintf (_file, " * "); \ +  retval += _eindent (_file); \ +  retval += vfprintf (_file, fmt, ap) + 3; \ +  if (colour_terminal ()) \ +    fprintf (_file, "\033[K"); + +static int _veinfon (const char *fmt, va_list ap) +{ +  int retval = 0; + +  VEINFON (stdout, EINFO_GOOD); +  return (retval); +} + +static int _vewarnn (const char *fmt, va_list ap) +{ +  int retval = 0; + +  VEINFON (stdout, EINFO_WARN); +  return (retval); +} + +static int _veerrorn (const char *fmt, va_list ap) +{ +  int retval = 0; + +  VEINFON (stderr, EINFO_BAD); +  return (retval); +} + +int einfon (const char *fmt, ...) +{ +  int retval; +  va_list ap; + +  if (! fmt || is_env ("RC_QUIET", "yes")) +    return (0); + +  va_start (ap, fmt); +  if (! (retval = ebuffer ("einfon", 0, fmt, ap))) +    retval = _veinfon (fmt, ap); +  va_end (ap); + +  return (retval); +} + +int ewarnn (const char *fmt, ...) +{ +  int retval; +  va_list ap; + +  if (! fmt || is_env ("RC_QUIET", "yes")) +    return (0); +  +  va_start (ap, fmt); +  if (! (retval = ebuffer ("ewarnn", 0, fmt, ap))) +    retval = _vewarnn (fmt, ap); +  va_end (ap); + +  return (retval); +} + +int eerrorn (const char *fmt, ...) +{ +  int retval; +  va_list ap; + +  va_start (ap, fmt); +  if (! (retval = ebuffer ("eerrorn", 0, fmt, ap))) +    retval = _veerrorn (fmt, ap); +  va_end (ap); + +  return (retval); +} + +int einfo (const char *fmt, ...) +{ +  int retval; +  va_list ap; + +  if (! fmt || is_env ("RC_QUIET", "yes")) +    return (0); + +  va_start (ap, fmt); +  if (! (retval = ebuffer ("einfo", 0, fmt, ap))) +    { +      retval = _veinfon (fmt, ap); +      retval += printf ("\n"); +    } +  va_end (ap); + +  return (retval); +} + +int ewarn (const char *fmt, ...) +{ +  int retval; +  va_list ap; + +  if (! fmt || is_env ("RC_QUIET", "yes")) +    return (0); + +  va_start (ap, fmt); +  elog (LOG_WARNING, fmt, ap); +  if (! (retval = ebuffer ("ewarn", 0, fmt, ap))) +    { +      retval = _vewarnn (fmt, ap); +      retval += printf ("\n"); +    } +  va_end (ap); + +  return (retval); +} + +void ewarnx (const char *fmt, ...) +{ +  int retval; +  va_list ap; + +  if (fmt && ! is_env ("RC_QUIET", "yes")) +    { +      va_start (ap, fmt); +      elog (LOG_WARNING, fmt, ap); +      retval = _vewarnn (fmt, ap); +      va_end (ap); +      retval += printf ("\n"); +    } +  exit (EXIT_FAILURE); +} + +int eerror (const char *fmt, ...) +{ +  int retval; +  va_list ap; + +  if (! fmt) +    return (0); + +  va_start (ap, fmt); +  elog (LOG_ERR, fmt, ap); +  retval = _veerrorn (fmt, ap); +  va_end (ap); +  retval += fprintf (stderr, "\n"); + +  return (retval); +} + +void eerrorx (const char *fmt, ...) +{ +  va_list ap; + +  if (fmt) +    { +      va_start (ap, fmt); +      elog (LOG_ERR, fmt, ap); +      _veerrorn (fmt, ap); +      va_end (ap); +      printf ("\n"); +    } +  exit (EXIT_FAILURE); +} + +int ebegin (const char *fmt, ...) +{ +  int retval; +  va_list ap; + +  if (! fmt || is_env ("RC_QUIET", "yes")) +    return (0); + +  va_start (ap, fmt); +  if ((retval = ebuffer ("ebegin", 0, fmt, ap))) +    { +      va_end (ap); +      return (retval); +    } + +  retval = _veinfon (fmt, ap); +  va_end (ap); +  retval += printf (" ..."); +  if (colour_terminal ()) +    retval += printf ("\n"); + +  return (retval); +} + +static void _eend (int col, einfo_color_t color, const char *msg) +{ +  FILE *fp = stdout; +  int i; +  int cols; +   +  if (! msg) +    return; + +  if (color == einfo_bad) +    fp = stderr; +   +  cols = get_term_columns () - (strlen (msg) + 6); + +  if (cols > 0 && colour_terminal ()) +    { +      fprintf (fp, "\033[A\033[%dC %s[ ", cols, EINFO_BRACKET); +      switch (color) +	{ +	case einfo_good: +	  fprintf (fp, EINFO_GOOD); +	  break; +	case einfo_warn: +	  fprintf (fp, EINFO_WARN); +	  break; +	case einfo_bad: +	  fprintf (fp, EINFO_BAD); +	  break; +	case einfo_hilite: +	  fprintf (fp, EINFO_HILITE); +	  break; +	case einfo_bracket: +	  fprintf (fp, EINFO_BRACKET); +	  break; +	case einfo_normal: +	  fprintf (fp, EINFO_NORMAL); +	  break; +	} +      fprintf (fp, "%s%s ]%s\n", msg, EINFO_BRACKET, EINFO_NORMAL); +    } +  else +    { +      for (i = -1; i < cols - col; i++) +	fprintf (fp, " "); +      fprintf (fp, "[ %s ]\n", msg); +    } +} + +static int _do_eend (const char *cmd, int retval, const char *fmt, va_list ap) +{ +  int col = 0; +  FILE *fp; + +  if (ebuffer (cmd, retval, fmt, ap)) +    return (retval); + +  if (fmt && retval != 0) +    { +      if (strcmp (cmd, "ewend") == 0) +	{ +	  col = _vewarnn (fmt, ap); +	  fp = stdout; +	} +      else +	{ +	  col = _veerrorn (fmt, ap); +	  fp = stderr; +	} +      if (colour_terminal ()) +	fprintf (fp, "\n"); +    } + +  _eend (col, retval == 0 ? einfo_good : einfo_bad, retval == 0 ? OK : NOT_OK); +  return (retval); +} + +int eend (int retval, const char *fmt, ...) +{ +  va_list ap; + +  if (is_env ("RC_QUIET", "yes")) +    return (retval); + +  va_start (ap, fmt); +  _do_eend ("eend", retval, fmt, ap); +  va_end (ap); + +  return (retval); +} +  +int ewend (int retval, const char *fmt, ...) +{ +  va_list ap; + +  if (is_env ("RC_QUIET", "yes")) +    return (retval); + +  va_start (ap, fmt); +  _do_eend ("ewend", retval, fmt, ap); +  va_end (ap); + +  return (retval); +} + +void ebracket (int col, einfo_color_t color, const char *msg) +{ +  _eend (col, color, msg); +} + +void eindent (void) +{ +  char *env = getenv ("RC_EINDENT"); +  int amount = 0; +  char num[10]; + +  if (ebuffer ("eindent", 0, NULL, NULL)) +    return; + +  if (env) +    { +      errno = 0; +      amount = strtol (env, NULL, 0); +      if (errno != 0) +	amount = 0; +    } + +  amount += INDENT_WIDTH; +  if (amount > INDENT_MAX) +    amount = INDENT_MAX; + +  snprintf (num, 10, "%08d", amount); +  setenv ("RC_EINDENT", num, 1); +} + +void eoutdent (void) +{ +  char *env = getenv ("RC_EINDENT"); +  int amount = 0; +  char num[10]; + +  if (ebuffer ("eoutdent", 0, NULL, NULL)) +    return; +   +  if (! env) +    return; +   +  errno = 0; +  amount = strtol (env, NULL, 0); +  if (errno != 0) +    amount = 0; +  else +    amount -= INDENT_WIDTH; + +  if (amount <= 0) +    unsetenv ("RC_EINDENT"); +  else +    { +      snprintf (num, 10, "%08d", amount); +      setenv ("RC_EINDENT", num, 1); +    } +} + +int veinfon (const char *fmt, ...) +{ +  int retval; +  va_list ap; + +  CHECK_VERBOSE; + +  if (! fmt) +    return (0); + +  va_start (ap, fmt); +  if (! (retval = ebuffer ("veinfon", 0, fmt, ap))) +    retval = _veinfon (fmt, ap); +  va_end (ap); + +  return (retval); +} + +int vewarnn (const char *fmt, ...) +{ +  int retval; +  va_list ap; + +  CHECK_VERBOSE; + +  if (! fmt) +    return (0); +  +  va_start (ap, fmt); +  if (! (retval = ebuffer ("vewarnn", 0, fmt, ap))) +    retval = _vewarnn (fmt, ap); +  va_end (ap); + +  return (retval); +} + +int veinfo (const char *fmt, ...) +{ +  int retval; +  va_list ap; + +  CHECK_VERBOSE; + +  if (! fmt) +    return (0); + +  va_start (ap, fmt); +  if (! (retval = ebuffer ("veinfo", 0, fmt, ap))) +    { +      retval = _veinfon (fmt, ap); +      retval += printf ("\n"); +    } + va_end (ap); + +  return (retval); +} + +int vewarn (const char *fmt, ...) +{ +  int retval; +  va_list ap; + +  CHECK_VERBOSE; + +  if (! fmt) +    return (0); + +  va_start (ap, fmt); +  if (! (retval = ebuffer ("vewarn", 0, fmt, ap))) +    { +      retval = _vewarnn (fmt, ap); +      retval += printf ("\n"); +    } +    va_end (ap); +  retval += printf ("\n"); + +  return (retval); +} + +int vebegin (const char *fmt, ...) +{ +  int retval; +  va_list ap; + +  CHECK_VERBOSE; + +  if (! fmt) +    return (0); + +  va_start (ap, fmt); +  if (! (retval = ebuffer ("vewarn", 0, fmt, ap))) +    { +      retval = _veinfon (fmt, ap); +      retval += printf (" ..."); +      if (colour_terminal ()) +	retval += printf ("\n"); +    } +  va_end (ap); + +  return (retval); +} + +int veend (int retval, const char *fmt, ...) +{ +  va_list ap; + +  CHECK_VERBOSE; + +  va_start (ap, fmt); +  _do_eend ("veend", retval, fmt, ap); +  va_end (ap); + +  return (retval); +} + +int vewend (int retval, const char *fmt, ...) +{ +  va_list ap; + +  CHECK_VERBOSE; + +  va_start (ap, fmt); +  _do_eend ("vewend", retval, fmt, ap); +  va_end (ap); + +  return (retval); +} + +void veindent (void) +{ +  if (is_env ("RC_VERBOSE", "yes")) +    eindent (); +} + +void veoutdent (void) +{ +  if (is_env ("RC_VERBOSE", "yes")) +    eoutdent (); +} diff --git a/src/librc-daemon.c b/src/librc-daemon.c new file mode 100644 index 00000000..02d0d937 --- /dev/null +++ b/src/librc-daemon.c @@ -0,0 +1,600 @@ +/* +   librc-daemon +   Finds PID for given daemon criteria +   Copyright 2007 Gentoo Foundation +   Released under the GPLv2 +   */ + +#include <sys/types.h> +#include <sys/stat.h> + +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined (__OpenBSD__)  +#include <sys/param.h> +#include <sys/user.h> +#include <sys/sysctl.h> +#include <kvm.h> +#include <limits.h> +#endif + +#ifndef __linux__ +#include <libgen.h> +#endif + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "einfo.h" +#include "rc.h" +#include "rc-misc.h" +#include "strlist.h" + +#if defined(__linux__) +static bool pid_is_cmd (pid_t pid, const char *cmd) +{ +  char buffer[32]; +  FILE *fp; +  int c; + +  snprintf(buffer, sizeof (buffer), "/proc/%d/stat", pid); +  if ((fp = fopen (buffer, "r")) == NULL) +    return (false); + +  while ((c = getc (fp)) != EOF && c != '(') +    ; + +  if (c != '(') +    { +      fclose(fp); +      return (false); +    } + +  while ((c = getc (fp)) != EOF && c == *cmd) +    cmd++; + +  fclose (fp); + +  return ((c == ')' && *cmd == '\0') ? true : false); +} + +static bool pid_is_exec (pid_t pid, const char *exec) +{ +  char cmdline[32]; +  char buffer[PATH_MAX]; +  char *p; +  int fd = -1; +  int r; + +  snprintf (cmdline, sizeof (cmdline), "/proc/%u/exe", pid); +  memset (buffer, 0, sizeof (buffer)); +  if (readlink (cmdline, buffer, sizeof (buffer)) != -1) +    { +      if (strcmp (exec, buffer) == 0) +	return (true); + +      /* We should cater for deleted binaries too */ +      if (strlen (buffer) > 10) +	{ +	  p = buffer + (strlen (buffer) - 10); +	  if (strcmp (p, " (deleted)") == 0) +	    { +	      *p = 0; +	      if (strcmp (buffer, exec) == 0) +		return (true); +	    } +	} +    } + +  snprintf (cmdline, sizeof (cmdline), "/proc/%u/cmdline", pid); +  if ((fd = open (cmdline, O_RDONLY)) < 0) +    return (false); + +  r = read(fd, buffer, sizeof (buffer)); +  close (fd); + +  if (r == -1) +    return 0; + +  buffer[r] = 0; +  return (strcmp (exec, buffer) == 0 ? true : false); +} + +pid_t *rc_find_pids (const char *exec, const char *cmd, +		     uid_t uid, pid_t pid) +{ +  DIR *procdir; +  struct dirent *entry; +  int npids = 0; +  int foundany = false; +  pid_t p; +  pid_t *pids = NULL; +  char buffer[PATH_MAX]; +  struct stat sb; +  pid_t runscript_pid = 0; +  char *pp; + +  if ((procdir = opendir ("/proc")) == NULL) +    eerrorx ("opendir `/proc': %s", strerror (errno)); + +  /* +     We never match RC_RUNSCRIPT_PID if present so we avoid the below +     scenario + +     /etc/init.d/ntpd stop does +     start-stop-daemon --stop --name ntpd +     catching /etc/init.d/ntpd stop + +     nasty +  */ + +  if ((pp = getenv ("RC_RUNSCRIPT_PID"))) +    { +      if (sscanf (pp, "%d", &runscript_pid) != 1) +	runscript_pid = 0; +    } + +  while ((entry = readdir (procdir)) != NULL) +    { +      if (sscanf (entry->d_name, "%d", &p) != 1) +	continue; +      foundany = true; + +      if (runscript_pid != 0 && runscript_pid == p) +	continue; + +      if (pid != 0 && pid != p) +	continue; + +      if (uid) +	{ +	  snprintf (buffer, sizeof (buffer), "/proc/%d", pid); +	  if (stat (buffer, &sb) != 0 || sb.st_uid != uid) +	    continue; +	} + +      if (cmd && ! pid_is_cmd (p, cmd)) +	continue; +      +      if (exec && ! cmd && ! pid_is_exec (p, exec)) +	continue; + +      pids = realloc (pids, sizeof (pid_t) * (npids + 2)); +      if (! pids) +	eerrorx ("memory exhausted"); + +      pids[npids] = p; +      pids[npids + 1] = 0; +      npids++; +    } +  closedir (procdir); + +  if (! foundany) +    eerrorx ("nothing in /proc"); + +  return (pids); +} + +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) + +# if defined(__FreeBSD__) +#  define _KINFO_PROC kinfo_proc +#  define _KVM_GETPROCS kvm_getprocs +#  define _KVM_GETARGV kvm_getargv +#  define _GET_KINFO_UID(kp) (kp.ki_ruid) +#  define _GET_KINFO_COMM(kp) (kp.ki_comm) +#  define _GET_KINFO_PID(kp) (kp.ki_pid) +# else +#  define _KINFO_PROC kinfo_proc2 +#  define _KVM_GETPROCS kvm_getprocs2 +#  define _KVM_GETARGV kvm_getargv2 +#  define _GET_KINFO_UID(kp) (kp.p_ruid) +#  define _GET_KINFO_COMM(kp) (kp.p_comm) +#  define _GET_KINFO_PID(kp) (kp.p_pid) +# endif + +pid_t *rc_find_pids (const char *exec, const char *cmd, +		     uid_t uid, pid_t pid) +{ +  static kvm_t *kd = NULL; +  char errbuf[_POSIX2_LINE_MAX]; +  struct _KINFO_PROC *kp; +  int i; +  int processes = 0; +  int argc = 0; +  char **argv; +  pid_t *pids = NULL; +  int npids = 0; + +  if ((kd = kvm_openfiles (NULL, NULL, NULL, O_RDONLY, errbuf)) == NULL) +    eerrorx ("kvm_open: %s", errbuf); + +  kp = _KVM_GETPROCS (kd, KERN_PROC_PROC, 0, &processes); +  for (i = 0; i < processes; i++) +    { +      pid_t p = _GET_KINFO_PID (kp[i]); +      if (pid != 0 && pid != p) +	continue; + +      if (uid != 0 && uid != _GET_KINFO_UID (kp[i])) +	continue; + +      if (cmd) +	{ +	  if (! _GET_KINFO_COMM (kp[i]) || +	      strcmp (cmd, _GET_KINFO_COMM (kp[i])) != 0) +	    continue; +	} + +      if (exec && ! cmd) +	{ +	  if ((argv = _KVM_GETARGV (kd, &kp[i], argc)) == NULL || ! *argv) +	    continue; + +	  if (strcmp (*argv, exec) != 0)  +	    continue; +	} + +      pids = realloc (pids, sizeof (pid_t) * (npids + 2)); +      if (! pids) +	eerrorx ("memory exhausted"); + +      pids[npids] = p; +      pids[npids + 1] = 0; +      npids++; +    } +  kvm_close(kd); + +  return (pids); +} + +#else +#  error "Platform not supported!" +#endif + +static bool _match_daemon (const char *path, const char *file, +			   const char *mexec, const char *mname, +			   const char *mpidfile) +{ +  char buffer[RC_LINEBUFFER]; +  char *ffile = rc_strcatpaths (path, file, NULL); +  FILE *fp; +  int lc = 0; +  int m = 0; + +  if (! rc_exists (ffile)) +    { +      free (ffile); +      return (false); +    } + +  if ((fp = fopen (ffile, "r")) == NULL) +    { +      eerror ("fopen `%s': %s", ffile, strerror (errno)); +      free (ffile); +      return (false); +    } + +  if (! mname) +    m += 10; +  if (! mpidfile) +    m += 100; + +  memset (buffer, 0, sizeof (buffer)); +  while ((fgets (buffer, RC_LINEBUFFER, fp))) +    { +      int lb = strlen (buffer) - 1; +      if (buffer[lb] == '\n') +	buffer[lb] = 0; + +      if (strcmp (buffer, mexec) == 0) +	m += 1; +      else if (mname && strcmp (buffer, mname) == 0) +	m += 10; +      else if (mpidfile && strcmp (buffer, mpidfile) == 0) +	m += 100; + +      if (m == 111) +	break; + +      lc++; +      if (lc > 5) +	break; +    } +  fclose (fp); +  free (ffile); + +  return (m == 111 ? true : false); +} + +void rc_set_service_daemon (const char *service, const char *exec, +			    const char *name, const char *pidfile, +			    bool started) +{ +  char *dirpath = rc_strcatpaths (RC_SVCDIR, "daemons", basename (service), NULL); +  char **files = NULL; +  char *file; +  char *ffile = NULL; +  int i; +  char *mexec; +  char *mname; +  char *mpidfile; +  int nfiles = 0; + +  if (! exec && ! name && ! pidfile) +    return; + +  if (exec) +    { +      i = strlen (exec) + 6; +      mexec = rc_xmalloc (sizeof (char *) * i); +      snprintf (mexec, i, "exec=%s", exec); +    } +  else +    mexec = strdup ("exec="); + +  if (name) +    { +      i = strlen (name) + 6; +      mname = rc_xmalloc (sizeof (char *) * i); +      snprintf (mname, i, "name=%s", name); +    } +  else +    mname = strdup ("name="); + +  if (pidfile) +    { +      i = strlen (pidfile) + 9; +      mpidfile = rc_xmalloc (sizeof (char *) * i); +      snprintf (mpidfile, i, "pidfile=%s", pidfile); +    } +  else +    mpidfile = strdup ("pidfile="); + +  /* Regardless, erase any existing daemon info */ +  if (rc_is_dir (dirpath)) +    { +      char *oldfile = NULL; +      files = rc_ls_dir (NULL, dirpath, 0); +      STRLIST_FOREACH (files, file, i) +	{ +	  ffile = rc_strcatpaths (dirpath, file, NULL); +	  nfiles++; + +	  if (! oldfile) +	    { +	      if (_match_daemon (dirpath, file, mexec, mname, mpidfile)) +		{ +		  unlink (ffile); +		  oldfile = ffile; +		  nfiles--; +		} +	    } +	  else +	    { +	      rename (ffile, oldfile); +	      free (oldfile); +	      oldfile = ffile; +	    } +	} +      if (ffile) +	free (ffile);  +      free (files); +    } + +  /* Now store our daemon info */ +  if (started) +    { +      char buffer[10]; +      FILE *fp; + +      if (! rc_is_dir (dirpath)) +	if (mkdir (dirpath, 0755) != 0) +	  eerror ("mkdir `%s': %s", dirpath, strerror (errno)); + +      snprintf (buffer, sizeof (buffer), "%03d", nfiles + 1); +      file = rc_strcatpaths (dirpath, buffer, NULL); +      if ((fp = fopen (file, "w")) == NULL) +	eerror ("fopen `%s': %s", file, strerror (errno)); +      else +	{ +	  fprintf (fp, "%s\n%s\n%s\n", mexec, mname, mpidfile); +	  fclose (fp); +	} +      free (file); +    } + +  free (mexec); +  free (mname); +  free (mpidfile); +  free (dirpath); +} + +bool rc_service_started_daemon (const char *service, const char *exec, +				int indx) +{ +  char *dirpath; +  char *file; +  int i; +  char *mexec; +  bool retval = false; + +  if (! service || ! exec) +    return (false); + +  dirpath = rc_strcatpaths (RC_SVCDIR, "daemons", basename (service), NULL); +  if (! rc_is_dir (dirpath)) +    { +      free (dirpath); +      return (false); +    } + +  i = strlen (exec) + 6; +  mexec = rc_xmalloc (sizeof (char *) * i); +  snprintf (mexec, i, "exec=%s", exec); + +  if (indx > 0) +    { +      file = rc_xmalloc (sizeof (char *) * 10); +      snprintf (file, sizeof (file), "%03d", indx); +      retval = _match_daemon (dirpath, file, mexec, NULL, NULL); +      free (file); +    } +  else +    { +      char **files = rc_ls_dir (NULL, dirpath, 0); +      STRLIST_FOREACH (files, file, i) +	{ +	  retval = _match_daemon (dirpath, file, mexec, NULL, NULL); +	  if (retval) +	    break; +	} +      free (files); +    } + +  free (mexec); +  return (retval); +} + +bool rc_service_daemons_crashed (const char *service) +{ +  char *dirpath; +  char **files; +  char *file; +  char *path; +  int i; +  FILE *fp; +  char buffer[RC_LINEBUFFER]; +  char *exec = NULL; +  char *name = NULL; +  char *pidfile = NULL; +  pid_t pid = 0; +  pid_t *pids = NULL; +  char *p; +  char *token; +  bool retval = false; + +  if (! service) +    return (false); + +  dirpath = rc_strcatpaths (RC_SVCDIR, "daemons", basename (service), NULL); +  if (! rc_is_dir (dirpath)) +    { +      free (dirpath); +      return (false); +    } + +  memset (buffer, 0, sizeof (buffer)); +  files = rc_ls_dir (NULL, dirpath, 0); +  STRLIST_FOREACH (files, file, i) +    { +      path = rc_strcatpaths (dirpath, file, NULL); +      fp = fopen (path, "r"); +      free (path); +      if (! fp) +	{ +	  eerror ("fopen `%s': %s", file, strerror (errno)); +	  continue; +	} + +      while ((fgets (buffer, RC_LINEBUFFER, fp))) +	{ +	  int lb = strlen (buffer) - 1; +	  if (buffer[lb] == '\n') +	    buffer[lb] = 0; + +	  p = buffer; +	  if ((token = strsep (&p, "=")) == NULL || ! p) +	    continue; + +	  if (strlen (p) == 0) +	    continue; + +	  if (strcmp (token, "exec") == 0) +	    { +	      if (exec) +		free (exec); +	      exec = strdup (p); +	    } +	  else if (strcmp (token, "name") == 0) +	    { +	      if (name) +		free (name); +	      name = strdup (p); +	    } +	  else if (strcmp (token, "pidfile") == 0) +	    { +	      if (pidfile) +		free (pidfile); +	      pidfile = strdup (p); +	    } +	} +      fclose (fp); + +      pid = 0; +      if (pidfile) +	{ +	  if (! rc_exists (pidfile)) +	    { +	      retval = true; +	      break; +	    } + +	  if ((fp = fopen (pidfile, "r")) == NULL) +	    { +		eerror ("fopen `%s': %s", pidfile, strerror (errno)); +		retval = true; +		break; +	    } + +	  if (fscanf (fp, "%d", &pid) != 1) +	    { +	      eerror ("no pid found in `%s'", pidfile); +	      fclose (fp); +	      retval = true; +	      break; +	    } + +	    fclose (fp); +	    free (pidfile); +	    pidfile = NULL; +	} + +      if ((pids = rc_find_pids (exec, name, 0, pid)) == NULL) +	{ +	  retval = true; +	  break; +	} +      free (pids); + +      if (exec) +	{ +	  free (exec); +	  exec = NULL; +	} +      if (name) +	{ +	  free (name); +	  name = NULL; +	} +    } + +  if (exec) +    { +      free (exec); +      exec = NULL; +    } +  if (name) +    { +      free (name); +      name = NULL; +    } + +  free (dirpath); +  rc_strlist_free (files); + +  return (retval); +} diff --git a/src/librc-depend.c b/src/librc-depend.c new file mode 100644 index 00000000..0da93aa5 --- /dev/null +++ b/src/librc-depend.c @@ -0,0 +1,838 @@ +/* +   librc-depend +   rc service dependency and ordering +   Copyright 2006-2007 Gentoo Foundation +   */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <err.h> +#include <errno.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "einfo.h" +#include "rc.h" +#include "rc-misc.h" +#include "strlist.h" + +#define GENDEP	RC_LIBDIR "/sh/gendepends.sh" + +/* We use this so we can pass our char array through many functions */ +struct lhead +{ +  char **list; +}; + +static char *get_shell_value (char *string) +{ +  char *p = string; +  char *e; + +  if (! string) +    return (NULL); + +  if (*p == '\'') +    p++; + +  e = p + strlen (p) - 1; +  if (*e == '\n') +    *e-- = 0; +  if (*e == '\'') +    *e-- = 0; + +  if (*p != 0) +    return p; + +  return (NULL); +} + +void rc_free_deptree (rc_depinfo_t *deptree) +{ +  rc_depinfo_t *di = deptree; +  while (di) +    { +      rc_depinfo_t *dip = di->next; +      rc_deptype_t *dt = di->depends; +      free (di->service); +      while (dt) +	{ +	  rc_deptype_t *dtp = dt->next; +	  free (dt->type);   +	  rc_strlist_free (dt->services); +	  free (dt); +	  dt = dtp; +	} +      free (di); +      di = dip; +    } +} + +rc_depinfo_t *rc_load_deptree (void) +{ +  FILE *fp; +  rc_depinfo_t *deptree = NULL; +  rc_depinfo_t *depinfo = NULL; +  rc_deptype_t *deptype = NULL; +  char buffer [RC_LINEBUFFER]; +  char *type; +  char *p; +  char *e; +  int i; + +  /* Update our deptree, but only if we need too */ +  rc_update_deptree (false); + +  if (! (fp = fopen (RC_DEPTREE, "r"))) +    return (NULL); + +  while (fgets (buffer, RC_LINEBUFFER, fp)) +    { +      p = buffer; +      e = strsep (&p, "_"); +      if (! e || strcmp (e, "depinfo") != 0) +	continue; + +      e = strsep (&p, "_"); +      if (! e || sscanf (e, "%d", &i) != 1) +	continue; + +      if (! (type = strsep (&p, "_="))) +	continue; + +      if (strcmp (type, "service") == 0) +	{ +	  /* Sanity */ +	  e = get_shell_value (p); +	  if (! e || strlen (e) == 0) +	    continue; + +	  if (! deptree) +	    { +	      deptree = rc_xmalloc (sizeof (rc_depinfo_t)); +	      depinfo = deptree; +	    } +	  else +	    { +	      depinfo->next = rc_xmalloc (sizeof (rc_depinfo_t)); +	      depinfo = depinfo->next; +	    } +	  memset (depinfo, 0, sizeof (rc_depinfo_t)); +	  depinfo->service = strdup (e); +	  deptype = NULL; +	  continue; +	} + +      e = strsep (&p, "="); +      if (! e || sscanf (e, "%d", &i) != 1) +	continue; + +      /* Sanity */ +      e = get_shell_value (p); +      if (! e || strlen (e) == 0) +	continue; + +      if (! deptype) +	{ +	  depinfo->depends = rc_xmalloc (sizeof (rc_deptype_t)); +	  deptype = depinfo->depends; +	  memset (deptype, 0, sizeof (rc_deptype_t)); +	} +      else +	if (strcmp (deptype->type, type) != 0) +	  { +	    deptype->next = rc_xmalloc (sizeof (rc_deptype_t)); +	    deptype = deptype->next; +	    memset (deptype, 0, sizeof (rc_deptype_t)); +	  } + +      if (! deptype->type) +	deptype->type = strdup (type); + +      deptype->services = rc_strlist_addsort (deptype->services, e); +    } +  fclose (fp); + +  return (deptree); +} + +rc_depinfo_t *rc_get_depinfo (rc_depinfo_t *deptree, const char *service) +{ +  rc_depinfo_t *di; + +  if (! deptree || ! service) +    return (NULL); + +  for (di = deptree; di; di = di->next) +    if (strcmp (di->service, service) == 0) +      return (di); + +  return (NULL); +} + +rc_deptype_t *rc_get_deptype (rc_depinfo_t *depinfo, const char *type) +{ +  rc_deptype_t *dt; + +  if (! depinfo || !type) +    return (NULL); + +  for (dt = depinfo->depends; dt; dt = dt->next) +    if (strcmp (dt->type, type) == 0) +      return (dt); + +  return (NULL); +} + +static bool valid_service (const char *runlevel, const char *service) +{ +  return ((strcmp (runlevel, RC_LEVEL_BOOT) != 0 && +	   rc_service_in_runlevel (service, RC_LEVEL_BOOT)) || +	  rc_service_in_runlevel (service, runlevel) || +	  rc_service_state (service, rc_service_coldplugged) || +	  rc_service_state (service, rc_service_started)); +} + +static bool get_provided1 (const char *runlevel, struct lhead *providers, +			   rc_deptype_t *deptype, +			   const char *level, bool coldplugged, +			   bool started, bool inactive) +{ +  char *service; +  int i; +  bool retval = false; + +  STRLIST_FOREACH (deptype->services, service, i) +    { +      bool ok = true; +      if (level) +	ok = rc_service_in_runlevel (service, level); +      else if (coldplugged) +	ok = (rc_service_state (service, rc_service_coldplugged) && +	      ! rc_service_in_runlevel (service, runlevel) && +	      ! rc_service_in_runlevel (service, RC_LEVEL_BOOT)); + +      if (! ok) +	continue; + +      if (started) +	ok = (rc_service_state (service, rc_service_starting) || +	      rc_service_state (service, rc_service_started) || +	      rc_service_state (service, rc_service_stopping)); +      else if (inactive) +	ok = rc_service_state (service, rc_service_inactive); + +      if (! ok) +	continue; +     +      retval = true; +      providers->list = rc_strlist_add (providers->list, service); +    } + +  return (retval); +} + +/* Work out if a service is provided by another service. +   For example metalog provides logger. +   We need to be able to handle syslogd providing logger too. +   We do this by checking whats running, then what's starting/stopping, +   then what's run in the runlevels and finally alphabetical order. + +   If there are any bugs in rc-depend, they will probably be here as +   provided dependancy can change depending on runlevel state. +   */ +static char **get_provided (rc_depinfo_t *deptree, rc_depinfo_t *depinfo, +			    const char *runlevel, int options) +{ +  rc_deptype_t *dt; +  struct lhead providers;  +  char *service; +  int i; + +  if (! deptree || ! depinfo) +    return (NULL); +  if (rc_service_exists (depinfo->service)) +    return (NULL); + +  dt = rc_get_deptype (depinfo, "providedby"); +  if (! dt) +    return (NULL); + +  memset (&providers, 0, sizeof (struct lhead)); +  /* If we are stopping then all depends are true, regardless of state. +     This is especially true for net services as they could force a restart +     of the local dns resolver which may depend on net. */ +  if (options & RC_DEP_STOP) +    { +      STRLIST_FOREACH (dt->services, service, i) +	providers.list = rc_strlist_add (providers.list, service); + +      return (providers.list); +    } + +  /* If we're strict, then only use what we have in our runlevel */ +  if (options & RC_DEP_STRICT) +    { +      STRLIST_FOREACH (dt->services, service, i) +	if (rc_service_in_runlevel (service, runlevel)) +	  providers.list = rc_strlist_add (providers.list, service); + +      if (providers.list) +	return (providers.list); +    } + +  /* OK, we're not strict or there were no services in our runlevel. +     This is now where the logic gets a little fuzzy :) +     If there is >1 running service then we return NULL. +     We do this so we don't hang around waiting for inactive services and +     our need has already been satisfied as it's not strict. +     We apply this to our runlevel, coldplugged services, then bootlevel +     and finally any running.*/ +#define DO \ +  if (providers.list && providers.list[0] && providers.list[1]) \ +    { \ +      rc_strlist_free (providers.list); \ +      return (NULL); \ +    } \ +  else if (providers.list)  \ +    return providers.list; \ + +  /* Anything in the runlevel has to come first */ +  if (get_provided1 (runlevel, &providers, dt, runlevel, false, true, false)) +    { DO } +  if (get_provided1 (runlevel, &providers, dt, runlevel, false, false, true)) +    { DO } +  if (get_provided1 (runlevel, &providers, dt, runlevel, false, false, false)) +    return (providers.list); + +  /* Check coldplugged started services */ +  if (get_provided1 (runlevel, &providers, dt, NULL, true, true, false)) +    { DO } + +  /* Check bootlevel if we're not in it */ +  if (strcmp (runlevel, RC_LEVEL_BOOT) != 0) +    { +      if (get_provided1 (runlevel, &providers, dt, RC_LEVEL_BOOT, false, true, false)) +	{ DO } +      if (get_provided1 (runlevel, &providers, dt, RC_LEVEL_BOOT, false, false, true)) +	{ DO } +    } + +  /* Check coldplugged inactive services */ +  if (get_provided1 (runlevel, &providers, dt, NULL, true, false, true)) +    { DO } + +  /* Check manually started */ +  if (get_provided1 (runlevel, &providers, dt, NULL, false, true, false)) +    { DO } +  if (get_provided1 (runlevel, &providers, dt, NULL, false, false, true)) +    { DO } + +  /* Nothing started then. OK, lets get the stopped services */ +  if (get_provided1 (runlevel, &providers, dt, NULL, true, false, false)) +    return (providers.list); +  if ((strcmp (runlevel, RC_LEVEL_BOOT) != 0) +      && (get_provided1 (runlevel, &providers, dt, RC_LEVEL_BOOT, false, false, false))) +    return (providers.list); + +  /* Still nothing? OK, list all services */ +  STRLIST_FOREACH (dt->services, service, i) +    providers.list = rc_strlist_add (providers.list, service); + +  return (providers.list); +} + +static void visit_service (rc_depinfo_t *deptree, char **types, +			   struct lhead *sorted, struct lhead *visited, +			   rc_depinfo_t *depinfo, +			   const char *runlevel, int options) +{ +  int i, j, k; +  char *lp, *item; +  char *service; +  rc_depinfo_t *di; +  rc_deptype_t *dt; +  char **provides; +  char *svcname; + +  if (! deptree || !sorted || !visited || !depinfo) +    return; + +  /* Check if we have already visited this service or not */ +  STRLIST_FOREACH (visited->list, item, i) +   if (strcmp (item, depinfo->service) == 0) +     return; + +  /* Add ourselves as a visited service */ +  visited->list = rc_strlist_add (visited->list, depinfo->service); + +  STRLIST_FOREACH (types, item, i) +    { +      if ((dt = rc_get_deptype (depinfo, item))) +	{ +	  STRLIST_FOREACH (dt->services, service, j) +	    { +	      if (! options & RC_DEP_TRACE || strcmp (item, "iprovide") == 0) +		{ +		  sorted->list = rc_strlist_add (sorted->list, service); +		  continue; +		} + +	      di = rc_get_depinfo (deptree, service); +	      if ((provides = get_provided (deptree, di, runlevel, options))) +		{ +		  STRLIST_FOREACH (provides, lp, k)  +		    { +		      di = rc_get_depinfo (deptree, lp); +		      if (di && (strcmp (item, "ineed") == 0 || +				 valid_service (runlevel, di->service))) +			visit_service (deptree, types, sorted, visited, di, +				       runlevel, options | RC_DEP_TRACE); +		    } +		  rc_strlist_free (provides); +		} +	      else +		if (di && (strcmp (item, "ineed") == 0 || +			   valid_service (runlevel, service))) +		  visit_service (deptree, types, sorted, visited, di, +				 runlevel, options | RC_DEP_TRACE); +	    } +	} +    } + +  /* Now visit the stuff we provide for */ +  if (options & RC_DEP_TRACE && (dt = rc_get_deptype (depinfo, "iprovide"))) +    { +      STRLIST_FOREACH (dt->services, service, i) +	{ +	  if ((di = rc_get_depinfo (deptree, service))) +	    if ((provides = get_provided (deptree, di, runlevel, options))) +	      { +		STRLIST_FOREACH (provides, lp, j) +		  if (strcmp (lp, depinfo->service) == 0) +		    { +		      visit_service (deptree, types, sorted, visited, di, +				     runlevel, options | RC_DEP_TRACE); +		      break; +		    } +		rc_strlist_free (provides); +	      } +	} +    } + +  /* We've visited everything we need, so add ourselves unless we +     are also the service calling us or we are provided by something */ +  svcname = getenv("SVCNAME"); +  if (! svcname || strcmp (svcname, depinfo->service) != 0) +    if (! rc_get_deptype (depinfo, "providedby")) +      sorted->list = rc_strlist_add (sorted->list, depinfo->service); +} + +char **rc_get_depends (rc_depinfo_t *deptree, +		       char **types, char **services, +		       const char *runlevel, int options) +{   +  struct lhead sorted; +  struct lhead visited; +  rc_depinfo_t *di; +  char *service; +  int i; + +  if (! deptree || ! types || ! services) +    return (NULL); + +  memset (&sorted, 0, sizeof (struct lhead)); +  memset (&visited, 0, sizeof (struct lhead)); + +  STRLIST_FOREACH (services, service, i) +    { +      di = rc_get_depinfo (deptree, service); +      visit_service (deptree, types, &sorted, &visited, di, runlevel, options); +    } + +  rc_strlist_free (visited.list); +  return (sorted.list); +} + +char **rc_order_services (rc_depinfo_t *deptree, const char *runlevel, +			  int options) +{ +  char **list = NULL; +  char **types = NULL; +  char **services = NULL; +  bool reverse = false; + +  if (! runlevel) +    return (NULL); + +  /* When shutting down, list all running services */ +  if (strcmp (runlevel, RC_LEVEL_SINGLE) == 0 || +      strcmp (runlevel, RC_LEVEL_SHUTDOWN) == 0 || +      strcmp (runlevel, RC_LEVEL_REBOOT) == 0) +    { +      list = rc_ls_dir (list, RC_SVCDIR_STARTING, RC_LS_INITD); +      list = rc_ls_dir (list, RC_SVCDIR_INACTIVE, RC_LS_INITD); +      list = rc_ls_dir (list, RC_SVCDIR_STARTED, RC_LS_INITD); +      reverse = true; +    } +  else +    { +      list = rc_services_in_runlevel (runlevel); + +      /* Add coldplugged services */ +      list = rc_ls_dir (list, RC_SVCDIR_COLDPLUGGED, RC_LS_INITD); + +      /* If we're not the boot runlevel then add that too */ +      if (strcmp (runlevel, RC_LEVEL_BOOT) != 0) +	{ +	  char *path = rc_strcatpaths (RC_RUNLEVELDIR, RC_LEVEL_BOOT, NULL); +	  list = rc_ls_dir (list, path, RC_LS_INITD); +	  free (path); +	} +    }  + +  /* Now we have our lists, we need to pull in any dependencies +     and order them */ +  types = rc_strlist_add (NULL, "ineed"); +  types = rc_strlist_add (types, "iuse"); +  types = rc_strlist_add (types, "iafter"); +  services = rc_get_depends (deptree, types, list, runlevel, +			     RC_DEP_STRICT | RC_DEP_TRACE | options); +  rc_strlist_free (list); +  rc_strlist_free (types); + +  if (reverse) +    rc_strlist_reverse (services); + +  return (services); +} + +static bool is_newer_than (const char *file, const char *target) +{ +  struct stat buf; +  int mtime; + +  if (stat (file, &buf) != 0 || buf.st_size == 0) +    return (false); +  mtime = buf.st_mtime; + +  if (stat (target, &buf) != 0) +    return (false); + +  if (mtime < buf.st_mtime) +    return (false); + +  if (rc_is_dir (target)) +    { +      char **targets = rc_ls_dir (NULL, target, 0); +      char *t; +      int i; +      bool newer = true; +      STRLIST_FOREACH (targets, t, i) +	{ +	  char *path = rc_strcatpaths (target, t, NULL); +	  newer = is_newer_than (file, path); +	  free (path); +	  if (! newer) +	    break; +	} +      rc_strlist_free (targets); +      return (newer); +    } + +  return (true); +} + +typedef struct deppair +{ +  const char *depend; +  const char *addto; +} deppair_t; + +static const deppair_t deppairs[] = { +    { "ineed",		"needsme" }, +    { "iuse",		"usesme" }, +    { "iafter",		"ibefore" }, +    { "ibefore",	"iafter" }, +    { "iprovide",	"providedby" }, +    { NULL, NULL } +}; + +static const char *depdirs[] = +{ +  RC_SVCDIR "starting", +  RC_SVCDIR "started", +  RC_SVCDIR "stopping", +  RC_SVCDIR "inactive", +  RC_SVCDIR "wasinactive", +  RC_SVCDIR "failed", +  RC_SVCDIR "coldplugged", +  RC_SVCDIR "daemons", +  RC_SVCDIR "options", +  RC_SVCDIR "exclusive", +  RC_SVCDIR "scheduled", +  RC_SVCDIR "ebuffer", +  NULL +}; + +/* This is a 5 phase operation +   Phase 1 is a shell script which loads each init script and config in turn +   and echos their dependency info to stdout +   Phase 2 takes that and populates a depinfo object with that data +   Phase 3 adds any provided services to the depinfo object +   Phase 4 scans that depinfo object and puts in backlinks +   Phase 5 saves the depinfo object to disk +   */ +int rc_update_deptree (bool force) +{ +  char *depends; +  char *service; +  char *type; +  char *depend; +  int retval = 0; +  FILE *fp; +  rc_depinfo_t *deptree; +  rc_depinfo_t *depinfo; +  rc_depinfo_t *di; +  rc_depinfo_t *last_depinfo = NULL; +  rc_deptype_t *deptype; +  rc_deptype_t *dt; +  rc_deptype_t *last_deptype = NULL; +  char buffer[RC_LINEBUFFER]; +  int len; +  int i; +  int j; +  int k; +  bool already_added; + +  /* Create base directories if needed */ +  for (i = 0; depdirs[i]; i++) +    if (! rc_is_dir (depdirs[i])) +      if (mkdir (depdirs[i], 0755) != 0) +	eerrorx ("mkdir `%s': %s", depdirs[i], strerror (errno)); + +  if (! force) +    if (is_newer_than (RC_DEPTREE, RC_INITDIR) && +	is_newer_than (RC_DEPTREE, RC_CONFDIR) && +	is_newer_than (RC_DEPTREE, "/etc/rc.conf")) +    return 0; + +  ebegin ("Caching service dependencies"); + +  /* Some init scripts need RC_LIBDIR to source stuff +     Ideally we should be setting our full env instead */ +  if (! getenv ("RC_LIBDIR")) +    setenv ("RC_LIBDIR", RC_LIBDIR, 0); + +  /* Phase 1 */ +  if ((fp = popen (GENDEP, "r")) == NULL) +    eerrorx ("popen: %s", strerror (errno)); + +  deptree = rc_xmalloc (sizeof (rc_depinfo_t)); +  memset (deptree, 0, sizeof (rc_depinfo_t)); +  memset (buffer, 0, RC_LINEBUFFER); + +  /* Phase 2 */ +  while (fgets (buffer, RC_LINEBUFFER, fp)) +    { +      /* Trim the newline */ +      if (buffer[strlen (buffer) - 1] == '\n') +	buffer[strlen(buffer) -1] = 0; + +      depends = buffer; +      service = strsep (&depends, " "); +      if (! service) +	continue; +      type = strsep (&depends, " "); + +      for (depinfo = deptree; depinfo; depinfo = depinfo->next) +	{ +	  last_depinfo = depinfo; +	  if (depinfo->service && strcmp (depinfo->service, service) == 0) +	    break; +	} + +      if (! depinfo) +	{ +	  if (! last_depinfo->service) +	    depinfo = last_depinfo; +	  else +	    { +	      last_depinfo->next = rc_xmalloc (sizeof (rc_depinfo_t)); +	      depinfo = last_depinfo->next; +	    } +	  memset (depinfo, 0, sizeof (rc_depinfo_t)); +	  depinfo->service = strdup (service); +	} + +      /* We may not have any depends */ +      if (! type || ! depends) +	continue; + +      last_deptype = NULL; +      for (deptype = depinfo->depends; deptype; deptype = deptype->next) +	{ +	  last_deptype = deptype; +	  if (strcmp (deptype->type, type) == 0) +	    break; +	} + +      if (! deptype) +	{ +	  if (! last_deptype) +	    { +	      depinfo->depends = rc_xmalloc (sizeof (rc_deptype_t)); +	      deptype = depinfo->depends; +	    } +	  else +	    { +	      last_deptype->next = rc_xmalloc (sizeof (rc_deptype_t)); +	      deptype = last_deptype->next; +	    } +	  memset (deptype, 0, sizeof (rc_deptype_t)); +	  deptype->type = strdup (type); +	} + +      /* Now add each depend to our type. +	 We do this individually so we handle multiple spaces gracefully */ +      while ((depend = strsep (&depends, " "))) +	{ +	  if (depend[0] == 0) +	    continue; + +	  /* .sh files are not init scripts */ +	  len = strlen (depend); +	  if (len > 2 && +	      depend[len - 3] == '.' && +	      depend[len - 2] == 's' && +	      depend[len - 1] == 'h') +	    continue; + +	  deptype->services = rc_strlist_addsort (deptype->services, depend); +	} + +    } +  pclose (fp); + +  /* Phase 3 - add our providors to the tree */ +  for (depinfo = deptree; depinfo; depinfo = depinfo->next) +    { +      if ((deptype = rc_get_deptype (depinfo, "iprovide"))) +	STRLIST_FOREACH (deptype->services, service, i) +	  { +	    for (di = deptree; di; di = di->next) +	      { +		last_depinfo = di; +		if (strcmp (di->service, service) == 0) +		  break; +	      } +	    if (! di) +	      { +		last_depinfo->next = rc_xmalloc (sizeof (rc_depinfo_t)); +		di = last_depinfo->next; +		memset (di, 0, sizeof (rc_depinfo_t)); +		di->service = strdup (service); +	      } +	  } +    } + +  /* Phase 4 - backreference our depends */ +  for (depinfo = deptree; depinfo; depinfo = depinfo->next) +    { +      for (i = 0; deppairs[i].depend; i++) +	{ +	  deptype = rc_get_deptype (depinfo, deppairs[i].depend); +	  if (! deptype) +	    continue; + +	  STRLIST_FOREACH (deptype->services, service, j) +	    { +	      di = rc_get_depinfo (deptree, service); +	      if (! di) +		{ +		  if (strcmp (deptype->type, "ineed") == 0) +		    { +		      eerror ("Service `%s' needs non existant service `%s'", +			      depinfo->service, service); +		      retval = -1; +		    }  +		  continue; +		} + +	      /* Add our deptype now */ +	      last_deptype = NULL; +	      for (dt = di->depends; dt; dt = dt->next) +		{ +		  last_deptype = dt; +		  if (strcmp (dt->type, deppairs[i].addto) == 0) +		    break; +		} +	      if (! dt) +		{ +		  if (! last_deptype) +		    { +		      di->depends = rc_xmalloc (sizeof (rc_deptype_t)); +		      dt = di->depends; +		    } +		  else +		    { +		      last_deptype->next = rc_xmalloc (sizeof (rc_deptype_t)); +		      dt = last_deptype->next; +		    } +		  memset (dt, 0, sizeof (rc_deptype_t)); +		  dt->type = strdup (deppairs[i].addto); +		} + +	      already_added = false; +	      STRLIST_FOREACH (dt->services, service, k) +	       if (strcmp (service, depinfo->service) == 0) +		 { +		   already_added = true; +		   break; +		 } + +	      if (! already_added) +		dt->services = rc_strlist_addsort (dt->services, +						   depinfo->service); +	    } +	} +    } + +  /* Phase 5 - save to disk +     Now that we're purely in C, do we need to keep a shell parseable file? +     I think yes as then it stays human readable +     This works and should be entirely shell parseable provided that depend +     names don't have any non shell variable characters in +     */ +  if ((fp = fopen (RC_DEPTREE, "w")) == NULL) +    eerror ("fopen `%s': %s", RC_DEPTREE, strerror (errno)); +  else +    { +      i = 0; +      for (depinfo = deptree; depinfo; depinfo = depinfo->next) +	{ +	  fprintf (fp, "depinfo_%d_service='%s'\n", i, depinfo->service); +	  for (deptype = depinfo->depends; deptype; deptype = deptype->next) +	    { +	      k = 0; +	      STRLIST_FOREACH (deptype->services, service, j) +		{ +		  fprintf (fp, "depinfo_%d_%s_%d='%s'\n", i, deptype->type, +			   k, service); +		  k++; +		} +	    } +	  i++; +	} +      fclose (fp); +    } + +  rc_free_deptree (deptree); + +  eend (retval, "Failed to update the service dependency tree"); +  return (retval); +} diff --git a/src/librc-misc.c b/src/librc-misc.c new file mode 100644 index 00000000..604c5518 --- /dev/null +++ b/src/librc-misc.c @@ -0,0 +1,750 @@ +/* +   rc-misc.c +   rc misc functions +   Copyright 2007 Gentoo Foundation +   */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/utsname.h> + +#include <dirent.h> +#include <errno.h> +#include <limits.h> +#include <regex.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "einfo.h" +#include "rc-misc.h" +#include "rc.h" +#include "strlist.h" + +#define ERRX 		eerrorx("out of memory"); + +#define PROFILE_ENV	"/etc/profile.env" +#define SYS_WHITELIST	RC_LIBDIR "conf.d/env_whitelist" +#define USR_WHITELIST	"/etc/conf.d/env_whitelist" +#define RC_CONFIG	"/etc/conf.d/rc" + +#define PATH_PREFIX 	RC_LIBDIR "bin:/bin:/sbin:/usr/bin:/usr/sbin" + +#ifndef S_IXUGO +# define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH) +#endif + +void *rc_xcalloc (size_t n, size_t size) +{ +  void *value = calloc (n, size); + +  if (value) +    return value; + +  ERRX +} + +void *rc_xmalloc (size_t size) +{ +  void *value = malloc (size); + +  if (value) +    return (value); + +  ERRX +} + +void *rc_xrealloc (void *ptr, size_t size) +{ +  void *value = realloc (ptr, size); + +  if (value) +    return (value); + +  ERRX +} + + +char *rc_xstrdup (const char *str) +{ +  char *value; + +  if (! str) +    return (NULL); + +  value = strdup (str); + +  if (value) +    return (value); + +  ERRX +} + +bool rc_is_env (const char *var, const char *val) +{ +  char *v; + +  if (! var) +    return (false); + +  v = getenv (var); +  if (! v) +    return (val == NULL ? true : false); + +  return (strcasecmp (v, val) == 0 ? true : false); +} + +char *rc_strcatpaths (const char *path1, const char *paths, ...) +{ +  va_list ap; +  int length; +  int i; +  char *p; +  char *path; +  char *pathp; + +  if (! path1 || ! paths) +    return (NULL); + +  length = strlen (path1) + strlen (paths) + 3; +  i = 0; +  va_start (ap, paths); +  while ((p = va_arg (ap, char *)) != NULL) +    length += strlen (p) + 1; +  va_end (ap); + +  path = rc_xmalloc (length); +  memset (path, 0, length); +  memcpy (path, path1, strlen (path1)); +  pathp = path + strlen (path1) - 1; +  if (*pathp != '/') +    { +      pathp++; +      *pathp++ = '/'; +    } +  else +    pathp++; +  memcpy (pathp, paths, strlen (paths)); +  pathp += strlen (paths); + +  va_start (ap, paths); +  while ((p = va_arg (ap, char *)) != NULL) +    { +      if (*pathp != '/') +	*pathp++ = '/'; +      i = strlen (p); +      memcpy (pathp, p, i); +      pathp += i; +    } +  va_end (ap); + +  *pathp++ = 0; + +  return (path); +} + +bool rc_exists (const char *pathname) +{ +  struct stat buf; + +  if (! pathname) +    return (false); + +  if (stat (pathname, &buf) == 0) +    return (true); + +  errno = 0; +  return (false); +} + +bool rc_is_file (const char *pathname) +{ +  struct stat buf; + +  if (! pathname) +    return (false); + +  if (stat (pathname, &buf) == 0) +    return (S_ISREG (buf.st_mode)); +     +  errno = 0; +  return (false); +} + +bool rc_is_dir (const char *pathname) +{ +  struct stat buf; + +  if (! pathname) +    return (false);  + +  if (stat (pathname, &buf) == 0) +    return (S_ISDIR (buf.st_mode)); + +  errno = 0; +  return (false); +} + +bool rc_is_link (const char *pathname) +{ +  struct stat buf; + +  if (! pathname) +    return (false);  + +  if (lstat (pathname, &buf) == 0) +    return (S_ISLNK (buf.st_mode)); +     +  errno = 0; +  return (false); +} + +bool rc_is_exec (const char *pathname) +{ +  struct stat buf; + +  if (! pathname) +    return (false); +   +  if (lstat (pathname, &buf) == 0) +    return (buf.st_mode & S_IXUGO); + +  errno = 0; +  return (false); +} + +char **rc_ls_dir (char **list, const char *dir, int options) +{ +  DIR *dp; +  struct dirent *d; + +  if (! dir) +    return (list); + +  if ((dp = opendir (dir)) == NULL) +    { +      eerror ("failed to opendir `%s': %s", dir, strerror (errno)); +      return (list); +    } + +  errno = 0; +  while (((d = readdir (dp)) != NULL) && errno == 0) +    { +      if (d->d_name[0] != '.') +	{ +	  if (options & RC_LS_INITD) +	    { +	      int l = strlen (d->d_name); +	      char *init = rc_strcatpaths (RC_INITDIR, d->d_name, NULL); +	      bool ok = rc_exists (init); +	      free (init); +	      if (! ok) +		continue; +	       +	      /* .sh files are not init scripts */ +	      if (l > 2 && d->d_name[l - 3] == '.' && +		  d->d_name[l - 2] == 's' && +		  d->d_name[l - 1] == 'h') +		continue; +	    } +	  list = rc_strlist_addsort (list, d->d_name); +	} +    } +  closedir (dp); + +  if (errno != 0) +    { +      eerror ("failed to readdir `%s': %s", dir, strerror (errno)); +      rc_strlist_free (list); +      return (NULL); +    } + +  return (list); +} + +bool rc_rm_dir (const char *pathname, bool top) +{ +  DIR *dp; +  struct dirent *d; + +  if (! pathname) +    return (false); + +  if ((dp = opendir (pathname)) == NULL) +    { +      eerror ("failed to opendir `%s': %s", pathname, strerror (errno)); +      return (false); +    } + +  errno = 0; +  while (((d = readdir (dp)) != NULL) && errno == 0) +    { +      if (strcmp (d->d_name, ".") != 0 && strcmp (d->d_name, "..") != 0) +	{ +	  char *tmp = rc_strcatpaths (pathname, d->d_name, NULL); +	  if (d->d_type == DT_DIR) +	    { +	      if (! rc_rm_dir (tmp, true)) +		{ +		  free (tmp); +		  closedir (dp); +		  return (false); +		} +	    } +	  else +	    { +	      if (unlink (tmp)) +		{ +		  eerror ("failed to unlink `%s': %s", tmp, strerror (errno)); +		  free (tmp); +		  closedir (dp); +		  return (false); +		} +	    } +	  free (tmp); +	} +    } +  if (errno != 0) +    eerror ("failed to readdir `%s': %s", pathname, strerror (errno)); +  closedir (dp); + +  if (top && rmdir (pathname) != 0) +    { +      eerror ("failed to rmdir `%s': %s", pathname, strerror (errno)); +      return false; +    } + +  return (true); +} + +char **rc_get_config (char **list, const char *file) +{ +  FILE *fp; +  char buffer[RC_LINEBUFFER]; +  char *p; +  char *token; +  char *line; +  char *linep; +  char *linetok; +  int i = 0; +  bool replaced; +  char *entry; +  char *newline; + +  if (! (fp = fopen (file, "r"))) +    { +      ewarn ("load_config_file `%s': %s", file, strerror (errno)); +      return (list); +    } + +  while (fgets (buffer, RC_LINEBUFFER, fp)) +    { +      p = buffer; + +      /* Strip leading spaces/tabs */ +      while ((*p == ' ') || (*p == '\t')) +	p++; + +      if (! p || strlen (p) < 3 || p[0] == '#') +	continue; + +      /* Get entry */ +      token = strsep (&p, "="); +      if (! token) +	continue; + +      entry = rc_xstrdup (token); + +      do +	{ +	  /* Bash variables are usually quoted */ +	  token = strsep (&p, "\"\'"); +	} +      while ((token) && (strlen (token) == 0)); + +      /* Drop a newline if that's all we have */ +      i = strlen (token) - 1; +      if (token[i] == 10) +	token[i] = 0; + +      i = strlen (entry) + strlen (token) + 2; +      newline = rc_xmalloc (i); +      snprintf (newline, i, "%s=%s", entry, token); + +      replaced = false; +      /* In shells the last item takes precedence, so we need to remove +	 any prior values we may already have */ +      STRLIST_FOREACH (list, line, i) +	{ +	  char *tmp = rc_xstrdup (line); +	  linep = tmp;  +	  linetok = strsep (&linep, "="); +	  if (strcmp (linetok, entry) == 0) +	    { +	      /* We have a match now - to save time we directly replace it */ +	      free (list[i - 1]); +	      list[i - 1] = newline;  +	      replaced = true; +	      free (tmp); +	      break; +	    } +	  free (tmp); +	} + +      if (! replaced) +	{ +	  list = rc_strlist_addsort (list, newline); +	  free (newline); +	} +      free (entry); +    } +  fclose (fp); + +  return (list); +} + +char *rc_get_config_entry (char **list, const char *entry) +{ +  char *line; +  int i; +  char *p; + +  STRLIST_FOREACH (list, line, i) +    { +      p = strchr (line, '='); +      if (p && strncmp (entry, line, p - line) == 0) +	return (p += 1); +    } + +  return (NULL); +} + +char **rc_get_list (char **list, const char *file) +{ +  FILE *fp; +  char buffer[RC_LINEBUFFER]; +  char *p; +  char *token; + +  if (! (fp = fopen (file, "r"))) +    { +      ewarn ("rc_get_list `%s': %s", file, strerror (errno)); +      return (list); +    } + +  while (fgets (buffer, RC_LINEBUFFER, fp)) +    { +      p = buffer; + +      /* Strip leading spaces/tabs */ +      while ((*p == ' ') || (*p == '\t')) +	p++; + +      /* Get entry - we do not want comments */ +      token = strsep (&p, "#"); +      if (token && (strlen (token) > 1)) +	{ +	  token[strlen (token) - 1] = 0; +	  list = rc_strlist_add (list, token); +	} +    } +  fclose (fp); + +  return (list); +} + +char **rc_filter_env (void) +{ +  char **env = NULL; +  char **whitelist = NULL; +  char *env_name = NULL; +  char **profile = NULL; +  int count = 0; +  bool got_path = false; +  char *env_var; +  int env_len; +  char *p; +  char *token; +  char *sep; +  char *e; +  int pplen = strlen (PATH_PREFIX); + +  whitelist = rc_get_list (whitelist, SYS_WHITELIST); +  if (! whitelist) +    ewarn ("system environment whitelist (" SYS_WHITELIST ") missing"); + +  whitelist = rc_get_list (whitelist, USR_WHITELIST); + +  if (! whitelist) +    return (NULL); + +  if (rc_is_file (PROFILE_ENV)) +    profile = rc_get_config (profile, PROFILE_ENV); + +  STRLIST_FOREACH (whitelist, env_name, count) +    { +      char *space = strchr (env_name, ' '); +      if (space) +	*space = 0; + +      env_var = getenv (env_name); + +      if (! env_var && profile) +	{ +	  env_len = strlen (env_name) + strlen ("export ") + 1; +	  p = rc_xmalloc (sizeof (char *) * env_len); +	  snprintf (p, env_len, "export %s", env_name); +	  env_var = rc_get_config_entry (profile, p); +	  free (p); +	} + +      if (! env_var) +	continue; + +      /* Ensure our PATH is prefixed with the system locations first +	 for a little extra security */ +       if (strcmp (env_name, "PATH") == 0 && +	   strncmp (PATH_PREFIX, env_var, pplen) != 0) +	{ +	  got_path = true; +	  env_len = strlen (env_name) + strlen (env_var) + pplen + 2; +	  e = p = rc_xmalloc (sizeof (char *) * env_len); +	  p += sprintf (e, "%s=%s", env_name, PATH_PREFIX); + +	  /* Now go through the env var and only add bits not in our PREFIX */ +	  sep = env_var; +	  while ((token = strsep (&sep, ":"))) +	    { +	      char *np = strdup (PATH_PREFIX); +	      char *npp = np; +	      char *tok = NULL; +	      while ((tok = strsep (&npp, ":"))) +		if (strcmp (tok, token) == 0) +		  break; +	      if (! tok) +		p += sprintf (p, ":%s", token); +	      free (np); +	    } +	  *p++ = 0; +	} +      else +	{ +	  env_len = strlen (env_name) + strlen (env_var) + 2; +	  e = rc_xmalloc (sizeof (char *) * env_len); +	  snprintf (e, env_len, "%s=%s", env_name, env_var); +	} + +      env = rc_strlist_add (env, e); +      free (e); +    } + +  /* We filtered the env but didn't get a PATH? Very odd. +     However, we do need a path, so use a default. */ +  if (! got_path) +    { +      env_len = strlen ("PATH=") + strlen (PATH_PREFIX) + 2; +      p = rc_xmalloc (sizeof (char *) * env_len); +      snprintf (p, env_len, "PATH=%s", PATH_PREFIX); +      env = rc_strlist_add (env, p); +      free (p); +    } + +  rc_strlist_free (whitelist); +  rc_strlist_free (profile); + +  return (env); +} + +/* Other systems may need this at some point, but for now it's Linux only */ +#ifdef __linux__ +static bool file_regex (const char *file, const char *regex) +{ +  FILE *fp; +  char buffer[RC_LINEBUFFER]; +  regex_t re; +  bool retval = false; +  int result; + +  if (! rc_exists (file)) +    return (false); + +  if (! (fp = fopen (file, "r"))) +    { +      ewarn ("file_regex `%s': %s", file, strerror (errno)); +      return (false); +    } + +  if ((result = regcomp (&re, regex, REG_EXTENDED | REG_NOSUB)) != 0) +    { +      fclose (fp); +      regerror (result, &re, buffer, sizeof (buffer)); +      eerror ("file_regex: %s", buffer); +      return (false); +    } + +  while (fgets (buffer, RC_LINEBUFFER, fp)) +    { +      if (regexec (&re, buffer, 0, NULL, 0) == 0) +	{ +	  retval = true; +	  break; +	} +    } +  fclose (fp); +  regfree (&re); + +  return (retval); +} +#endif + +char **rc_config_env (char **env) +{ +  char *line; +  int i; +  char *p; +  char **config = rc_get_config (NULL, RC_CONFIG); +  char *e; +  char sys[6]; +  struct utsname uts; +  bool has_net_fs_list = false; +  FILE *fp; +  char buffer[PATH_MAX]; + +  STRLIST_FOREACH (config, line, i) +    { +      p = strchr (line, '='); +      if (! p) +	continue; + +      *p = 0; +      e = getenv (line); +      if (! e) +	{ +	  *p = '='; +	  env = rc_strlist_add (env, line); +	} +      else +	{ +	  int len = strlen (line) + strlen (e) + 2; +	  char *new = rc_xmalloc (sizeof (char *) * len); +	  snprintf (new, len, "%s=%s", line, e); +	  env = rc_strlist_add (env, new); +	  free (new); +	} +    } +  rc_strlist_free (config); + +  i = strlen ("RC_LIBDIR=//rcscripts") + strlen (LIBDIR) + 2; +  line = rc_xmalloc (sizeof (char *) * i); +  snprintf (line, i, "RC_LIBDIR=/" LIBDIR "/rcscripts"); +  env = rc_strlist_add (env, line); +  free (line); +   +  i += strlen ("/init.d"); +  line = rc_xmalloc (sizeof (char *) * i); +  snprintf (line, i, "RC_SVCDIR=/" LIBDIR "/rcscripts/init.d"); +  env = rc_strlist_add (env, line); +  free (line); + +  env = rc_strlist_add (env, "RC_BOOTLEVEL=" RC_LEVEL_BOOT); + +  p = rc_get_runlevel (); +  i = strlen ("RC_SOFTLEVEL=") + strlen (p) + 1; +  line = rc_xmalloc (sizeof (char *) * i); +  snprintf (line, i, "RC_SOFTLEVEL=%s", p); +  env = rc_strlist_add (env, line); +  free (line); + +  if (rc_exists (RC_SVCDIR "ksoftlevel")) +    { +      if (! (fp = fopen (RC_SVCDIR "ksoftlevel", "r"))) +	eerror ("fopen `%s': %s", RC_SVCDIR "ksoftlevel", +		strerror (errno)); +      else +	{ +	  memset (buffer, 0, sizeof (buffer)); +	  if (fgets (buffer, sizeof (buffer), fp)) +	    { +	      i = strlen (buffer) - 1; +	      if (buffer[i] == '\n') +		buffer[i] = 0; +	      i += strlen ("RC_DEFAULTLEVEL=") + 2; +	      line = rc_xmalloc (sizeof (char *) * i); +	      snprintf (line, i, "RC_DEFAULTLEVEL=%s", buffer); +	      env = rc_strlist_add (env, line); +	      free (line); +	    } +	  fclose (fp); +	} +    } +  else +    env = rc_strlist_add (env, "RC_DEFAULTLEVEL=" RC_LEVEL_DEFAULT); + +  memset (sys, 0, sizeof (sys)); + +/* Linux can run some funky stuff like Xen, VServer, UML, etc +   We store this special system in RC_SYS so our scripts run fast */ +#ifdef __linux__ +  if (rc_is_dir ("/proc/xen")) +    { +      fp = fopen ("/proc/xen/capabilities", "r"); +      if (fp) +	{ +	  fclose (fp); +	  if (file_regex ("/proc/xen/capabilities", "control_d")) +	    sprintf (sys, "XENU"); +	} +      if (! sys) +	sprintf (sys, "XEN0"); +    } +  else if (file_regex ("/proc/cpuinfo", "UML")) +    sprintf (sys, "UML"); +  else if (file_regex ("/proc/self/status", +		       "(s_context|VxID|envID):[[:space:]]*[1-9]")) +    sprintf(sys, "VPS");  +#endif + +  /* Only add a NET_FS list if not defined */ +  STRLIST_FOREACH (env, line, i) +    if (strncmp (line, "RC_NET_FS_LIST=", strlen ("RC_NET_FS_LIST=")) == 0) +      { +	has_net_fs_list = true; +	break; +      } +  if (! has_net_fs_list) +    { +      i = strlen ("RC_NET_FS_LIST=") + strlen (RC_NET_FS_LIST_DEFAULT) + 1; +      line = rc_xmalloc (sizeof (char *) * i); +      snprintf (line, i, "RC_NET_FS_LIST=%s", RC_NET_FS_LIST_DEFAULT); +      env = rc_strlist_add (env, line); +      free (line); +    } + +  if (sys[0]) +    { +      i = strlen ("RC_SYS=") + strlen (sys) + 2; +      line = rc_xmalloc (sizeof (char *) * i); +      snprintf (line, i, "RC_SYS=%s", sys); +      env = rc_strlist_add (env, line); +      free (line); +    } +  +  /* Some scripts may need to take a different code path if Linux/FreeBSD, etc +     To save on calling uname, we store it in an environment variable */ +  if (uname (&uts) == 0) +    { +      i = strlen ("RC_UNAME=") + strlen (uts.sysname) + 2; +      line = rc_xmalloc (sizeof (char *) * i); +      snprintf (line, i, "RC_UNAME=%s", uts.sysname); +      env = rc_strlist_add (env, line); +      free (line); +    } + +  /* Set this var to ensure that things are POSIX, which makes scripts work +     on non GNU systems with less effort. */ +  env = rc_strlist_add (env, "POSIXLY_CORRECT=1"); + +  return (env); +} diff --git a/src/librc-strlist.c b/src/librc-strlist.c new file mode 100644 index 00000000..981f654b --- /dev/null +++ b/src/librc-strlist.c @@ -0,0 +1,141 @@ +/* +   librc-strlist.h +   String list functions for using char ** arrays + +   Copyright 2007 Gentoo Foundation +   Based on a previous implementation by Martin Schlemmer +   Released under the GPLv2 +   */ + +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +#include "rc.h" +#include "rc-misc.h" + +char **rc_strlist_add (char **list, const char *item) +{ +  char **newlist; +  int i = 0; + +  if (! item) +    return (list); + +  while (list && list[i]) +    i++; + +  newlist = rc_xrealloc (list, sizeof (char *) * (i + 2)); +  newlist[i] = rc_xstrdup (item); +  newlist[i + 1] = NULL; + +  return (newlist); +} + +static char **_rc_strlist_addsort (char **list, const char *item, +				   int (*sortfunc) (const char *s1, +						    const char *s2)) +{ +  char **newlist; +  int i = 0; +  char *tmp1; +  char *tmp2; + +  if (! item) +    return (list); + +  while (list && list[i]) +    i++; + +  newlist = rc_xrealloc (list, sizeof (char *) * (i + 2)); + +  if (i == 0) +    newlist[i] = NULL; +  newlist[i + 1] = NULL; + +  i = 0; +  while (newlist[i] && sortfunc (newlist[i], item) < 0) +    i++; + +  tmp1 = newlist[i]; +  newlist[i] = rc_xstrdup (item); +  do +    { +      i++; +      tmp2 = newlist[i]; +      newlist[i] = tmp1; +      tmp1 = tmp2; +    } while (tmp1); + +  return (newlist); +} + +char **rc_strlist_addsort (char **list, const char *item) +{ +  return (_rc_strlist_addsort (list, item, strcoll)); +} + +char **rc_strlist_addsortc (char **list, const char *item) +{ +  return (_rc_strlist_addsort (list, item, strcmp)); +} + +char **rc_strlist_delete (char **list, const char *item) +{ +  int i = 0; + +  if (!list || ! item) +    return (list); + +  while (list[i]) +    if (strcmp (list[i], item) == 0) +      { +	free (list[i]); +	do +	  { +	    list[i] = list[i + 1]; +	    i++; +	  } while (list[i]); +      } + +  return (list); +} + +void rc_strlist_reverse (char **list) +{ +  char *item; +  int i = 0; +  int j = 0; + +  if (! list) +    return; + +  while (list[j]) +    j++; +  j--; + +  while (i < j && list[i] && list[j]) +    { +      item = list[i]; +      list[i] = list[j]; +      list[j] = item; +      i++; +      j--; +    } +} + +void rc_strlist_free (char **list) +{ +  int i = 0; + +  if (! list) +    return; + +  while (list[i]) +    { +      free (list[i]); +      list[i++] = NULL; +    } + +  free (list); +} diff --git a/src/librc.c b/src/librc.c new file mode 100644 index 00000000..d9c4a539 --- /dev/null +++ b/src/librc.c @@ -0,0 +1,773 @@ +/* +   librc  +   core RC functions +   Copyright 2007 Gentoo Foundation +   Released under the GPLv2 +   */ + +#include <sys/types.h> +#include <sys/select.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <errno.h> +#ifndef __linux__ +/* Although linux should work fine, gcc likes to bitch with our default +   CFLAGS so we just don't include the file and use the GNU one defined +   in string.h */ +#include <libgen.h> +#endif +#include <limits.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "einfo.h" +#include "rc.h" +#include "rc-misc.h" +#include "strlist.h" + +/* usecs to wait while we poll the fifo */ +#define WAIT_INTERVAL	20000 + +/* max secs to wait until a service comes up */ +#define WAIT_MAX	60 + +#define SOFTLEVEL	RC_SVCDIR "softlevel" + +static const char *rc_service_state_names[] = { +  "started", +  "stopped", +  "starting", +  "stopping", +  "inactive", +  "wasinactive", +  "coldplugged", +  "failed", +  NULL +}; + +bool rc_runlevel_starting (void) +{ +  return (rc_is_dir (RC_SVCDIR "softscripts.old")); +} + +bool rc_runlevel_stopping (void) +{ +  return (rc_is_dir (RC_SVCDIR "softscripts.new")); +} + +char **rc_get_runlevels (void) +{ +  char **dirs = rc_ls_dir (NULL, RC_RUNLEVELDIR, 0); +  char **runlevels = NULL; +  int i; +  char *dir; + +  STRLIST_FOREACH (dirs, dir, i) +    { +      char *path = rc_strcatpaths (RC_RUNLEVELDIR, dir, NULL); +      if (rc_is_dir (path)) +	runlevels = rc_strlist_addsort (runlevels, dir); +      free (path); +    } +  rc_strlist_free (dirs); + +  return (runlevels); +} + +char *rc_get_runlevel (void) +{ +  FILE *fp; +  static char buffer [PATH_MAX]; + +  if (! (fp = fopen (SOFTLEVEL, "r"))) +    { +      strcpy (buffer, "sysinit"); +      return (buffer); +    } + +  if (fgets (buffer, PATH_MAX, fp)) +    { +      int i = strlen (buffer) - 1; +      if (buffer[i] == '\n') +	buffer[i] = 0; +      fclose (fp); +      return (buffer); +    } + +  fclose (fp); +  strcpy (buffer, "sysinit"); +  return (buffer); +} + +void rc_set_runlevel (const char *runlevel) +{ +  FILE *fp = fopen (SOFTLEVEL, "w"); +  if (! fp) +    eerrorx ("failed to open `" SOFTLEVEL "': %s", strerror (errno)); +  fprintf (fp, "%s", runlevel); +  fclose (fp); +} + +bool rc_runlevel_exists (const char *runlevel) +{ +  char *path; +  bool retval; + +  if (! runlevel) +    return (false); + +  path = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, NULL); +  retval = rc_is_dir (path); +  free (path); +  return (retval); +} + +/* Resolve a service name to it's full path */ +char *rc_resolve_service (const char *service) +{ +  char buffer[PATH_MAX]; +  char *file; +  int r = 0; + +  if (! service) +    return (NULL); + +  if (service[0] == '/') +    return (strdup (service)); + +  file = rc_strcatpaths (RC_SVCDIR, "started", service, NULL); +  if (! rc_is_link (file)) +    { +      free (file); +      file = rc_strcatpaths (RC_SVCDIR, "inactive", service, NULL); +      if (! rc_is_link (file)) +	{ +	  free (file); +	  file = NULL; +	} +    } + +  memset (buffer, 0, sizeof (buffer)); +  if (file) +    { +      r = readlink (file, buffer, sizeof (buffer)); +      free (file); +      if (r > 0) +	return strdup (buffer); +    } + +  snprintf (buffer, sizeof (buffer), RC_INITDIR "%s", service); +  return (strdup (buffer)); +} + +bool rc_service_exists (const char *service) +{ +  char *file; +  bool retval = false; +  int len; + +  if (! service) +    return (false); + +  len = strlen (service); + +  /* .sh files are not init scripts */ +  if (len > 2 && service[len - 3] == '.' && +      service[len - 2] == 's' && +      service[len - 1] == 'h') +    return (false); + +  file = rc_resolve_service (service);  +  if (rc_exists (file)) +    retval = rc_is_exec (file); +  free (file); +  return (retval); +} + +bool rc_service_in_runlevel (const char *service, const char *runlevel) +{ +  char *file; +  bool retval; + +  if (! runlevel || ! service) +    return (false); + +  if (! rc_service_exists (service)) +    return (false); + +  file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename (service), NULL); +  retval = rc_exists (file); +  free (file); + +  return (retval); +} + +bool rc_mark_service (const char *service, const rc_service_state_t state) +{ +  char *file; +  int i = 0; +  int skip_state = -1; +  char *base; +  char *init = rc_resolve_service (service); +  bool skip_wasinactive = false; + +  if (! service) +    return (false); + +  base = basename (service); + +  if (state != rc_service_stopped) +    { +      if (! rc_is_file(init)) +	{ +	  free (init); +	  return (false); +	} + +      file = rc_strcatpaths (RC_SVCDIR, rc_service_state_names[state], base, NULL); +      if (rc_exists (file)) +	unlink (file); +      i = symlink (init, file); +      if (i != 0) +	{ +	  free (file); +	  free (init); +	  einfo ("%d %s %s", state, rc_service_state_names[state], base); +	  eerror ("symlink `%s' to `%s': %s", init, file, strerror (errno)); +	  return (false); +	} + +      free (file); +      skip_state = state; +    } + +  if (state == rc_service_coldplugged) +    { +      free (init); +      return (true); +    } + +  /* Remove any old states now */ +  i = 0; +  while (rc_service_state_names[i]) +    { +      if ((i != skip_state && +	   i != rc_service_stopped && +	   i != rc_service_coldplugged && +	   i != rc_service_crashed) && +	 (! skip_wasinactive || i != rc_service_wasinactive)) +	{ +	  file = rc_strcatpaths (RC_SVCDIR, rc_service_state_names[i], base, NULL); +	  if (rc_exists (file)) +	    { +	      if ((state == rc_service_starting || +		   state == rc_service_stopping) && +		  i == rc_service_inactive) +		{ +		  char *wasfile = rc_strcatpaths (RC_SVCDIR, +						  rc_service_state_names[rc_service_wasinactive], +						  base, NULL); + +		  if (symlink (init, wasfile) != 0) +		    eerror ("symlink `%s' to `%s': %s", init, wasfile, +			    strerror (errno)); + +		  skip_wasinactive = true; +		  free (wasfile); +		} + +	      errno = 0; +	      if (unlink (file) != 0 && errno != ENOENT) +		eerror ("failed to delete `%s': %s", file, +			strerror (errno)); +	    } +	  free (file); +	} +      i++; +    } + +  /* Remove the exclusive state if we're inactive */ +  if (state == rc_service_started || +      state == rc_service_stopped || +      state == rc_service_inactive) +    { +      file = rc_strcatpaths (RC_SVCDIR, "exclusive", base, NULL); +      if (rc_exists (file)) +	if (unlink (file) != 0) +	  eerror ("unlink `%s': %s", file, strerror (errno)); +      free (file); +    } + +  /* Remove any options and daemons the service may have stored */ +  if (state == rc_service_stopped) +    { +      char *dir = rc_strcatpaths (RC_SVCDIR, "options", base, NULL); + +      if (rc_is_dir (dir)) +	rc_rm_dir (dir, true); +      free (dir); + +      dir = rc_strcatpaths (RC_SVCDIR, "daemons", base, NULL); +      if (rc_is_dir (dir)) +	rc_rm_dir (dir, true); +      free (dir); +       +      rc_schedule_clear (service); +    } + +  /* These are final states, so remove us from scheduled */ +  if (state == rc_service_started || state == rc_service_stopped) +    { +      char *sdir = rc_strcatpaths (RC_SVCDIR, "scheduled", NULL); +      char **dirs = rc_ls_dir (NULL, sdir, 0); +      char *dir; +      int serrno; + +      STRLIST_FOREACH (dirs, dir, i) +	{ +	  char *bdir = rc_strcatpaths (sdir, dir, NULL); +	  file = rc_strcatpaths (bdir, base, NULL); +	  if (rc_exists (file)) +	    if (unlink (file) != 0) +	      eerror ("unlink `%s': %s", file, strerror (errno)); +	  free (file); + +	  /* Try and remove the dir - we don't care about errors */ +	  serrno = errno; +	  rmdir (bdir); +	  errno = serrno; +	  free (bdir); +	} +      rc_strlist_free (dirs); +      free (sdir); +    } + +  free (init); +  return (true); +} + +bool rc_service_state (const char *service, const rc_service_state_t state) +{ +  char *file; +  bool retval; + +  /* If the init script does not exist then we are stopped */ +  if (! rc_service_exists (service)) +    return (state == rc_service_stopped ? true : false); + +  /* We check stopped state by not being in any of the others */ +  if (state == rc_service_stopped) +    return ( ! (rc_service_state (service, rc_service_started) || +		rc_service_state (service, rc_service_starting) || +		rc_service_state (service, rc_service_stopping) || +		rc_service_state (service, rc_service_inactive))); + +  /* The crashed state and scheduled states are virtual */ +  if (state == rc_service_crashed) +    return (rc_service_daemons_crashed (service)); +  else if (state == rc_service_scheduled) +    { +      char **services = rc_services_scheduled_by (service); +      retval = (services); +      if (services) +	free (services); +      return (retval); +    } + +  /* Now we just check if a file by the service name rc_exists +     in the state dir */ +  file = rc_strcatpaths (RC_SVCDIR, rc_service_state_names[state], +		      basename (service), NULL); +  retval = rc_exists (file); +  free (file); +  return (retval); +} + +bool rc_get_service_option (const char *service, const char *option, +			    char *value) +{ +  FILE *fp; +  char buffer[1024]; +  char *file = rc_strcatpaths (RC_SVCDIR, "options", service, option, NULL); +  bool retval = false; + +  if (rc_exists (file)) +    { +      if ((fp = fopen (file, "r")) == NULL) +	eerror ("fopen `%s': %s", file, strerror (errno)); +      else +	{ +	  memset (buffer, 0, sizeof (buffer)); +	  while (fgets (buffer, RC_LINEBUFFER, fp)) +	    { +	      memcpy (value, buffer, strlen (buffer)); +	      value += strlen (buffer); +	    } +	  fclose (fp); +	  retval = true; +	} +    } + +  free (file); +  return (retval); +} + +bool rc_set_service_option (const char *service, const char *option, +			    const char *value) +{ +  FILE *fp; +  char *path = rc_strcatpaths (RC_SVCDIR, "options", service, NULL); +  char *file = rc_strcatpaths (path, option, NULL); +  bool retval = false; + +  if (! rc_is_dir (path)) +    { +      if (mkdir (path, 0755) != 0) +	{ +	  eerror ("mkdir `%s': %s", path, strerror (errno)); +	  free (path); +	  free (file); +	  return (false); +	} +    } + +  if ((fp = fopen (file, "w")) == NULL) +    eerror ("fopen `%s': %s", file, strerror (errno)); +  else +    { +      if (value) +	fprintf (fp, "%s", value); +      fclose (fp); +      retval = true; +    } + +  free (path); +  free (file); +  return (retval); +} + +static pid_t _exec_service (const char *service, const char *arg) +{ +  char *file; +  char *fifo; +  pid_t pid = -1; +  pid_t savedpid; +  int status; + +  file = rc_resolve_service (service); +  if (! rc_is_file (file)) +    { +      rc_mark_service (service, rc_service_stopped); +      free (file); +      return (0); +    } + +  /* We create a fifo so that other services can wait until we complete */ +  fifo = rc_strcatpaths (RC_SVCDIR, "exclusive", basename (service), NULL); + +  if (mkfifo (fifo, 0600) != 0 && errno != EEXIST) +    { +      eerror ("unable to create fifo `%s': %s", fifo, strerror (errno)); +      free (fifo); +      free (file); +      return (-1); +    } + +  if ((pid = fork ()) == 0) +    { +      char *myarg = strdup (arg); +      int e = 0; +      execl (file, file, myarg, NULL); +      e = errno; +      free (myarg); +      unlink (fifo); +      free (fifo); +      eerrorx ("unable to exec `%s': %s", file, strerror (errno)); +    } + +  free (fifo); +  free (file); + +  if (pid == -1) +    { +      eerror ("unable to fork: %s", strerror (errno)); +      return (pid); +    } + +  if (rc_is_env ("RC_PARALLEL_STARTUP", "yes")) +    return (pid); + +  savedpid = pid; +  errno = 0; +  do +    { +      pid = waitpid (savedpid, &status, 0); +      if (pid < 0) +	{ +	  if (errno != ECHILD) +	    eerror ("waitpid %d: %s", savedpid, strerror (errno)); +	  return (-1); +	} +    } while (! WIFEXITED (status) && ! WIFSIGNALED (status));  + +  return (0); +} + +pid_t rc_stop_service (const char *service) +{ +  if (rc_service_state (service, rc_service_stopped)) +    return (0); + +  return (_exec_service (service, "stop")); +} + + +pid_t rc_start_service (const char *service) +{ +  if (! rc_service_state (service, rc_service_stopped)) +    return (0); + +  return (_exec_service (service, "start")); +} + +void rc_schedule_start_service (const char *service, +				const char *service_to_start) +{ +  char *dir; +  char *init; +  char *file; + +  if (! rc_service_exists (service) || ! rc_service_exists (service_to_start)) +    return; + +  dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename (service), NULL); +  if (! rc_is_dir (dir)) +    if (mkdir (dir, 0755) != 0) +      { +	eerror ("mkdir `%s': %s", dir, strerror (errno)); +	free (dir); +	return; +      } + +  init = rc_resolve_service (service_to_start); +  file = rc_strcatpaths (dir, basename (service_to_start), NULL); +  if (! rc_exists (file) && symlink (init, file) != 0) +    eerror ("symlink `%s' to `%s': %s", init, file, strerror (errno)); + +  free (init); +  free (file); +  free (dir); +} + +void rc_schedule_clear (const char *service) +{ +  char *dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename (service), NULL); + +  if (rc_is_dir (dir)) +    rc_rm_dir (dir, true); +  free (dir); +} + +bool rc_wait_service (const char *service) +{ +  char *fifo = rc_strcatpaths (RC_SVCDIR, "exclusive", basename (service), NULL); +  struct timeval tv; +  struct timeval stopat; +  struct timeval now; +  bool retval = false; + +  if (gettimeofday (&stopat, NULL) != 0) +    { +      eerror ("gettimeofday: %s", strerror (errno)); +      return (false); +    } +  stopat.tv_sec += WAIT_MAX; + +  while (true) +    { +      if (! rc_exists (fifo)) +	{ +	  retval = true; +	  break; +	} + +      tv.tv_sec = 0; +      tv.tv_usec = WAIT_INTERVAL; +      if (select (0, 0, 0, 0, &tv) < 0) +	{ +	  if (errno != EINTR) +	    eerror ("select: %s",strerror (errno)); +	  break; +	} + +      /* Don't hang around forever */ +      if (gettimeofday (&now, NULL) != 0) +	{ +	  eerror ("gettimeofday: %s", strerror (errno)); +	  break; +	} +      if (timercmp (&now, &stopat, >)) +	break; +    } + +  free (fifo); +  return (retval); +} + +char **rc_services_in_runlevel (const char *runlevel) +{ +  char *dir; +  char **list = NULL; + +  if (! runlevel) +    return (rc_ls_dir (NULL, RC_INITDIR, RC_LS_INITD)); + +  /* These special levels never contain any services */ +  if (strcmp (runlevel, RC_LEVEL_SYSINIT) == 0 || +      strcmp (runlevel, RC_LEVEL_SINGLE) == 0) +    return (NULL); + +  dir = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, NULL); +  if (! rc_is_dir (dir)) +      eerror ("runlevel `%s' does not exist", runlevel); +  else +    list = rc_ls_dir (list, dir, RC_LS_INITD); + +  free (dir); +  return (list); +} + +char **rc_services_in_state (rc_service_state_t state) +{ +  char *dir = rc_strcatpaths (RC_SVCDIR, rc_service_state_names[state], NULL); +  char **list = NULL; + +  if (rc_is_dir (dir)) +    list = rc_ls_dir (list, dir, RC_LS_INITD); + +  free (dir); +  return (list); +} + +bool rc_service_add (const char *runlevel, const char *service) +{ +  bool retval; +  char *init; +  char *file; + +  if (! rc_runlevel_exists (runlevel)) +    { +      errno = ENOENT; +      return (false); +    } +   +  if (rc_service_in_runlevel (service, runlevel)) +    { +      errno = EEXIST; +      return (false); +    } + +  init = rc_resolve_service (service); +  file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename (service), NULL); +  retval = (symlink (init, file) == 0); +  free (init); +  free (file); +  return (retval); +} + +bool rc_service_delete (const char *runlevel, const char *service) +{ +  char *file; +  bool retval = false; + +  if (! runlevel || ! service) +    return (false); +    +  file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename (service), NULL); +  if (unlink (file) == 0) +    retval = true; +     +  free (file); +  return (retval); +} + +char **rc_services_scheduled_by (const char *service) +{ +  char **dirs = rc_ls_dir (NULL, RC_SVCDIR "scheduled", 0); +  char **list = NULL; +  char *dir; +  int i; + +  STRLIST_FOREACH (dirs, dir, i) +    { +      char *file = rc_strcatpaths (RC_SVCDIR "scheduled", dir, service, NULL); +      if (rc_exists (file)) +	list = rc_strlist_add (list, file); +      free (file); +    } +  rc_strlist_free (dirs); +   +  return (list); +} + +char **rc_services_scheduled (const char *service) +{ +  char *dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename (service), NULL); +  char **list = NULL; + +  if (rc_is_dir (dir)) +    list = rc_ls_dir (list, dir, RC_LS_INITD); + +  free (dir); +  return (list); +} + +bool rc_allow_plug (char *service) +{ +  char *list; +  char *p; +  char *star; +  char *token; +  bool allow = true; +  char *match = getenv ("RC_PLUG_SERVICES"); +  if (! match) +    return true; + +  list = strdup (match); +  p = list; +  while ((token = strsep (&p, " "))) +    { +      bool truefalse = true; +      if (token[0] == '!') +	{ +	  truefalse = false; +	  token++; +	} + +      star = strchr (token, '*'); +      if (star) +	{ +	  if (strncmp (service, token, star - token) == 0) +	    { +	      allow = truefalse; +	      break; +	    } +	} +      else +	{ +	  if (strcmp (service, token) == 0) +	    { +	      allow = truefalse; +	      break; +	    } +	} +    } + +  free (list); +  return (allow); +} diff --git a/src/mountinfo.c b/src/mountinfo.c new file mode 100644 index 00000000..1fc84420 --- /dev/null +++ b/src/mountinfo.c @@ -0,0 +1,246 @@ +/* +   mountinfo.c +   Obtains information about mounted filesystems. + +   Copyright 2007 Gentoo Foundation +   */ + +#include <sys/types.h> +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) +#include <sys/param.h> +#include <sys/ucred.h> +#include <sys/mount.h> +#elif defined(__linux__) +#include <limits.h> +#endif + +#include <errno.h> +#include <limits.h> +#include <regex.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "einfo.h" +#include "rc.h" +#include "rc-misc.h" +#include "strlist.h" + +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined (__OpenBSD__) +static char **find_mounts (regex_t *node_regex, regex_t *fstype_regex, +			   char **mounts, bool list_nodes, bool list_fstype) +{ +  struct statfs *mnts; +  int nmnts; +  int i; +  char **list = NULL; + +  if ((nmnts = getmntinfo (&mnts, MNT_NOWAIT)) == 0) +	eerrorx ("getmntinfo: %s", strerror (errno)); + +  for (i = 0; i < nmnts; i++) +    { +      if (node_regex && +	  regexec (node_regex, mnts[i].f_mntfromname, 0, NULL, 0) != 0) +	continue; +      if (fstype_regex && +	  regexec (fstype_regex, mnts[i].f_fstypename, 0, NULL, 0) != 0) +	continue; + +      if (mounts) +	{ +	  bool found = false; +	  int j; +	  char *mnt; +	  STRLIST_FOREACH (mounts, mnt, j) +	   if (strcmp (mnt, mnts[i].f_mntonname) == 0) +	     { +	       found = true; +	       break; +	     } +	  if (! found) +	    continue; +	} + +      list = rc_strlist_addsortc (list, list_nodes ? +				  mnts[i].f_mntfromname : +				  list_fstype ? mnts[i].f_fstypename : +				  mnts[i].f_mntonname); +    } + +  return (list); +} + +#elif defined (__linux__) +static char **find_mounts (regex_t *node_regex, regex_t *fstype_regex, +			   char **mounts, bool list_nodes, bool list_fstype) +{ +  FILE *fp; +  char buffer[PATH_MAX * 3]; +  char *p; +  char *from; +  char *to; +  char *fstype; +  char **list = NULL; +   +  if ((fp = fopen ("/proc/mounts", "r")) == NULL) +	eerrorx ("getmntinfo: %s", strerror (errno)); + +  while (fgets (buffer, sizeof (buffer), fp)) +    { +      p = buffer; +      from = strsep (&p, " "); +      if (node_regex && +	  regexec (node_regex, from, 0, NULL, 0) != 0) +	continue; +       +      to = strsep (&p, " "); +      fstype = strsep (&p, " "); +      /* Skip the really silly rootfs */ +      if (strcmp (fstype, "rootfs") == 0) +	continue; +      if (fstype_regex && +	  regexec (fstype_regex, fstype, 0, NULL, 0) != 0) +	continue; + +      if (mounts) +	{ +	  bool found = false; +	  int j; +	  char *mnt; +	  STRLIST_FOREACH (mounts, mnt, j) +	   if (strcmp (mnt, to) == 0) +	     { +	       found = true; +	       break; +	     } +	  if (! found) +	    continue; +	} + +      list = rc_strlist_addsortc (list, +				  list_nodes ?  +				  list_fstype ? fstype : +				  from : to); +    } +  fclose (fp); + +  return (list); +} + +#else +#  error "Operating system not supported!" +#endif + +int main (int argc, char **argv) +{ +  int i; +  regex_t *fstype_regex = NULL; +  regex_t *node_regex = NULL; +  regex_t *skip_regex = NULL; +  char **nodes = NULL; +  char *node; +  int result; +  char buffer[256]; +  bool list_nodes = false; +  bool list_fstype = false; +  bool reverse = false; +  char **mounts = NULL; + +  for (i = 1; i < argc; i++) +    { +      if (strcmp (argv[i], "--fstype-regex") == 0 && (i + 1 < argc)) +	{ +	  i++; +	  if (fstype_regex) +	    free (fstype_regex); +	  fstype_regex = rc_xmalloc (sizeof (regex_t)); +	  if ((result = regcomp (fstype_regex, argv[i], +				REG_EXTENDED | REG_NOSUB)) != 0) +	    { +	      regerror (result, fstype_regex, buffer, sizeof (buffer)); +	      eerrorx ("%s: invalid regex `%s'", argv[0], buffer); +	    } +	    continue; +	} + +      if (strcmp (argv[i], "--node-regex") == 0 && (i + 1 < argc)) +	{ +	  i++; +	  if (node_regex) +	    free (node_regex); +	  node_regex = rc_xmalloc (sizeof (regex_t)); +	  if ((result = regcomp (node_regex, argv[i], +				REG_EXTENDED | REG_NOSUB)) != 0) +	    { +	      regerror (result, node_regex, buffer, sizeof (buffer)); +	      eerrorx ("%s: invalid regex `%s'", argv[0], buffer); +	    } +	  continue; +	} + +      if (strcmp (argv[i], "--skip-regex") == 0 && (i + 1 < argc)) +	{ +	  i++; +	  if (skip_regex) +	    free (skip_regex); +	  skip_regex = rc_xmalloc (sizeof (regex_t)); +	  if ((result = regcomp (skip_regex, argv[i], +				REG_EXTENDED | REG_NOSUB)) != 0) +	    { +	      regerror (result, skip_regex, buffer, sizeof (buffer)); +	      eerrorx ("%s: invalid regex `%s'", argv[0], buffer); +	    } +	  continue; +	} + +      if (strcmp (argv[i], "--fstype") == 0) +	{ +	  list_fstype = true; +	  continue; +	} + +      if (strcmp (argv[i], "--node") == 0) +	{ +	  list_nodes = true; +	  continue; +	} +      if (strcmp (argv[i], "--reverse") == 0) +	{ +	  reverse = true; +	  continue; +	} + +      if (argv[i][0] != '/') +	eerrorx ("%s: `%s' is not a mount point", argv[0], argv[i]); + +      mounts = rc_strlist_add (mounts, argv[i]); +    } + +  nodes = find_mounts (node_regex, fstype_regex, mounts, +		       list_nodes, list_fstype); + +  if (node_regex) +    regfree (node_regex); +  if (fstype_regex) +    regfree (fstype_regex); + +  if (reverse) +    rc_strlist_reverse (nodes); + +  result = EXIT_FAILURE; +  STRLIST_FOREACH (nodes, node, i) +    { +      if (skip_regex && regexec (skip_regex, node, 0, NULL, 0) == 0) +	continue; +      printf ("%s\n", node); +      result = EXIT_SUCCESS; +    } +  rc_strlist_free (nodes); + +  if (skip_regex) +    free (skip_regex); + +  exit (result); +} + diff --git a/src/rc-depend.c b/src/rc-depend.c new file mode 100644 index 00000000..9d0b3af8 --- /dev/null +++ b/src/rc-depend.c @@ -0,0 +1,120 @@ +/* +   rc-depend +   rc service dependency and ordering +   Copyright 2006-2007 Gentoo Foundation +   Released under the GPLv2 +   */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "einfo.h" +#include "rc.h" +#include "rc-misc.h" +#include "strlist.h" + +int main (int argc, char **argv) +{ +  char **types = NULL; +  char **services = NULL; +  char **depends = NULL; +  rc_depinfo_t *deptree = NULL; +  rc_depinfo_t *di; +  char *service; +  int options = RC_DEP_TRACE; +  bool first = true; +  int i; +  bool update = false; +  char *runlevel = getenv ("RC_SOFTLEVEL"); + +  if (! runlevel) +    runlevel = rc_get_runlevel (); + +  for (i = 1; i < argc; i++) +    { +      if (strcmp (argv[i], "--update") == 0) +	{ +	  if (! update) +	    { +	      rc_update_deptree (true); +	      update = true; +	    } +	  continue; +	} + +      if (strcmp (argv[i], "--strict") == 0) +	{ +	  options |= RC_DEP_STRICT; +	  continue; +	} + +      if (strcmp (argv[i], "--notrace") == 0) +	{ +	  options &= RC_DEP_TRACE; +	  continue; +	} + +      if (argv[i][0] == '-') +	{ +	  argv[i]++; +	  types = rc_strlist_add (types, argv[i]); +	} +      else +	{ +	  if ((deptree = rc_load_deptree ()) == NULL) +	    eerrorx ("failed to load deptree"); + +	  di = rc_get_depinfo (deptree, argv[i]); +	  if (! di) +	    eerror ("no dependency info for service `%s'", argv[i]); +	  else +	    services = rc_strlist_add (services, argv[i]); +	} +    } + +  if (! services) +    { +      rc_strlist_free (types); +      rc_free_deptree (deptree); +      if (update) +	return (EXIT_SUCCESS); +      eerrorx ("no services specified"); +    } + +  /* If we don't have any types, then supply some defaults */ +  if (! types) +    { +      types = rc_strlist_add (NULL, "ineed"); +      rc_strlist_add (types, "iuse"); +    } + +  depends = rc_get_depends (deptree, types, services, runlevel, options); + +  if (depends) +    { +      STRLIST_FOREACH (depends, service, i) +	{ +	  if (first) +	    first = false; +	  else +	    printf (" "); + +	  if (service) +	    printf ("%s", service); + +	} +      printf ("\n"); +    } + +  rc_strlist_free (types); +  rc_strlist_free (services); +  rc_strlist_free (depends); +  rc_free_deptree (deptree); + +  return (EXIT_SUCCESS); +} diff --git a/src/rc-misc.h b/src/rc-misc.h new file mode 100644 index 00000000..5a4aa55f --- /dev/null +++ b/src/rc-misc.h @@ -0,0 +1,34 @@ +/* +   rc-misc.h +   This is private to us and not for user consumption +   Copyright 2007 Gentoo Foundation +   */ + +#ifndef __RC_MISC_H__ +#define __RC_MISC_H__ + +#ifndef LIBDIR +#  define LIBDIR	"lib" +#endif + +#define RC_LIBDIR	"/" LIBDIR "/rcscripts/" +#define RC_SVCDIR	RC_LIBDIR "init.d/" +#define RC_DEPTREE 	RC_SVCDIR "deptree" +#define RC_RUNLEVELDIR 	"/etc/runlevels/" +#define RC_INITDIR	"/etc/init.d/" +#define RC_CONFDIR	"/etc/conf.d/" + +#define RC_SVCDIR_STARTING	RC_SVCDIR "starting/" +#define RC_SVCDIR_INACTIVE	RC_SVCDIR "inactive/" +#define RC_SVCDIR_STARTED	RC_SVCDIR "started/" +#define RC_SVCDIR_COLDPLUGGED	RC_SVCDIR "coldplugged/" + +#define RC_PLUGINDIR	RC_LIBDIR "plugins/" + +/* Max buffer to read a line from a file */ +#define RC_LINEBUFFER	4096  + +/* Good defaults just incase user has none set */ +#define RC_NET_FS_LIST_DEFAULT "afs cifs coda davfs fuse gfs ncpfs nfs nfs4 ocfs2 shfs smbfs" + +#endif diff --git a/src/rc-plugin.c b/src/rc-plugin.c new file mode 100644 index 00000000..c02b6a81 --- /dev/null +++ b/src/rc-plugin.c @@ -0,0 +1,119 @@ +/* +   librc-plugin.c  +   Simple plugin handler +   Copyright 2007 Gentoo Foundation +   Released under the GPLv2 +   */ + +#include <dlfcn.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "einfo.h" +#include "rc.h" +#include "rc-misc.h" +#include "rc-plugin.h" +#include "strlist.h" + +typedef struct plugin +{ +  char *name; +  void *handle; +  int (*hook) (rc_hook_t hook, const char *name); +  struct plugin *next; +} plugin_t; + +static plugin_t *plugins = NULL; + +void rc_plugin_load (void) +{ +  char **files; +  char *file; +  int i; +  plugin_t *plugin = plugins; + +  /* Ensure some sanity here */ +  rc_plugin_unload (); + +  if (! rc_exists (RC_PLUGINDIR)) +    return; + +  files = rc_ls_dir (NULL, RC_PLUGINDIR, 0); +  STRLIST_FOREACH (files, file, i) +    { +      char *p = rc_strcatpaths (RC_PLUGINDIR, file, NULL); +      void *h = dlopen (p, RTLD_LAZY); +      char *func; +      void *f; + +      if (! h) +	{ +	  eerror ("dlopen `%s': %s", p, dlerror ()); +	  free (p); +	  continue; +	} + +      func = file; +      file = strsep (&func, "."); +      func = rc_xmalloc (strlen (file) + strlen ("__hook") + 1); +      sprintf (func, "_%s_hook", file); + +      f = dlsym (h, func); +      if (! f) +	{ +	  eerror ("`%s' does not expose the symbol `%s'", p, func); +	  dlclose (h); +	} +      else +	{ +	  if (plugin) +	    { +	      plugin->next = rc_xmalloc (sizeof (plugin_t)); +	      plugin = plugin->next; +	    } +	  else +	    plugin = plugins = rc_xmalloc (sizeof (plugin_t)); + +	  memset (plugin, 0, sizeof (plugin_t)); +	  plugin->name = strdup (file); +	  plugin->handle = h; +	  plugin->hook = f; +	} + +      free (func); +      free (p); +    } + +  rc_strlist_free (files); +} + +void rc_plugin_run (rc_hook_t hook, const char *value) +{ +  plugin_t *plugin = plugins; + +  while (plugin) +    { +      if (plugin->hook) +	plugin->hook (hook, value); + +      plugin = plugin->next; +    } +} + +void rc_plugin_unload (void) +{ +  plugin_t *plugin = plugins; +  plugin_t *next; + +  while (plugin) +    { +      next = plugin->next; +      dlclose (plugin->handle); +      free (plugin->name); +      free (plugin); +      plugin = next; +    } +  plugins = NULL; +} diff --git a/src/rc-plugin.h b/src/rc-plugin.h new file mode 100644 index 00000000..b4391ad0 --- /dev/null +++ b/src/rc-plugin.h @@ -0,0 +1,15 @@ +/* +   librc-plugin.h  +   Private instructions to use plugins +   Copyright 2007 Gentoo Foundation +   Released under the GPLv2 +   */ + +#ifndef __LIBRC_PLUGIN_H__ +#define __LIBRC_PLUGIN_H__ + +void rc_plugin_load (); +void rc_plugin_unload (); +void rc_plugin_run (rc_hook_t, const char *value); + +#endif diff --git a/src/rc-status.c b/src/rc-status.c new file mode 100644 index 00000000..5f2f9b1d --- /dev/null +++ b/src/rc-status.c @@ -0,0 +1,142 @@ +/* +   rc-status +   Display the status of the services in runlevels +   Copyright 2007 Gentoo Foundation +   Released under the GPLv2 +   */ + +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "einfo.h" +#include "rc.h" +#include "rc-misc.h" +#include "strlist.h" + +static void print_level (char *level) +{ +  printf ("Runlevel: "); +  PEINFO_HILITE; +  printf ("%s\n", level); +  PEINFO_NORMAL; +} + +static void print_service (char *service) +{ +  char status[10]; +  int cols =  printf (" %s\n", service); +  einfo_color_t color = einfo_bad; + +  if (rc_service_state (service, rc_service_stopping)) +    snprintf (status, sizeof (status),   "stopping "); +  else if (rc_service_state (service, rc_service_starting)) +    { +      snprintf (status, sizeof (status), "starting "); +      color = einfo_warn; +    } +  else if (rc_service_state (service, rc_service_inactive)) +    { +      snprintf (status, sizeof (status), "inactive "); +      color = einfo_warn; +    } +  else if (geteuid () == 0 && rc_service_state (service, rc_service_crashed)) +    snprintf (status, sizeof (status),   " crashed "); +  else if (rc_service_state (service, rc_service_started)) +    { +      snprintf (status, sizeof (status), " started "); +      color = einfo_good; +    } +  else if (rc_service_state (service, rc_service_scheduled)) +    { +      snprintf (status, sizeof (status), "scheduled"); +      color = einfo_warn; +    } +  else +    snprintf (status, sizeof (status),   " stopped "); +  ebracket (cols, color, status); +} + +int main (int argc, char **argv) +{ +  char **levels = NULL;  +  char **services = NULL; +  char *level; +  char *service; +  char c; +  int option_index = 0; +  int i; +  int j; + +  const struct option longopts[] = +    { +	{"all", no_argument, NULL, 'a'}, +	{"list", no_argument, NULL, 'l'}, +	{"servicelist", no_argument, NULL, 's'}, +	{"unused", no_argument, NULL, 'u'}, +	{NULL, 0, NULL, 0} +    }; + +  while ((c = getopt_long(argc, argv, "alsu", longopts, &option_index)) != -1) +    switch (c) +      { +      case 'a': +	levels = rc_get_runlevels (); +	break; +      case 'l': +	levels = rc_get_runlevels (); +	STRLIST_FOREACH (levels, level, i) +	 printf ("%s\n", level); +	rc_strlist_free (levels); +	exit (EXIT_SUCCESS); +      case 's': +	services = rc_services_in_runlevel (NULL); +	STRLIST_FOREACH (services, service, i) +	 print_service (service); +	rc_strlist_free (services); +	exit (EXIT_SUCCESS); +      case 'u': +	services = rc_services_in_runlevel (NULL); +	levels = rc_get_runlevels (); +	STRLIST_FOREACH (services, service, i) +	  { +	    bool found = false; +	    STRLIST_FOREACH (levels, level, j) +	      if (rc_service_in_runlevel (service, level)) +		{ +		  found = true; +		  break; +		} +	    if (! found) +	      print_service (service); +	  } +	rc_strlist_free (levels); +	rc_strlist_free (services); +	exit (EXIT_SUCCESS); +      case '?': +	exit (EXIT_FAILURE); +      default: +	exit (EXIT_FAILURE); +      } + +  while (optind < argc) +    levels = rc_strlist_add (levels, argv[optind++]); + +  if (! levels) +    levels = rc_strlist_add (NULL, rc_get_runlevel ()); + +  STRLIST_FOREACH (levels, level, i) +    { +      print_level (level); +      services = rc_services_in_runlevel (level); +      STRLIST_FOREACH (services, service, j) +       print_service (service); +      rc_strlist_free (services); +    } + +  rc_strlist_free (levels); + +  return (EXIT_SUCCESS); +} diff --git a/src/rc-update.c b/src/rc-update.c new file mode 100644 index 00000000..8d50a4af --- /dev/null +++ b/src/rc-update.c @@ -0,0 +1,162 @@ +/* +   rc-update +   Manage init scripts and runlevels +   Copyright 2007 Gentoo Foundation +   Released under the GPLv2 +   */ + +#include <errno.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "einfo.h" +#include "rc.h" +#include "rc-misc.h" +#include "strlist.h" + +static char *applet = NULL; + +static bool add (const char *runlevel, const char *service) +{ +  bool retval = true; + +  if (! rc_runlevel_exists (runlevel)) +    { +      ewarn ("runlevel `%s' does not exist", runlevel); +      return (false); +    } +  if (rc_service_in_runlevel (service, runlevel)) +    { +      ewarn ("%s already installed in runlevel `%s'; skipping", +	     service, runlevel); +      return (false); +    } + +  if (rc_service_add (runlevel, service)) +    einfo ("%s added to runlevel %s", service, runlevel); +  else +   { +      eerror ("%s: failed to add service `%s' to runlevel `%s': %s", +	      applet, service, runlevel, strerror (errno)); +      retval = false; +    } + +  return (retval); +} + +int main (int argc, char **argv) +{ +  int i; +  int j; +  char *service; +  char **runlevels = NULL; +  char *runlevel; + +  applet = argv[0]; +  if (argc < 2 || +      strcmp (argv[1], "show") == 0 || +      strcmp (argv[1], "-s") == 0) +    { +      bool verbose = false; +      char **services = rc_services_in_runlevel (NULL); + +      for (i = 2; i < argc; i++) +	{ +	  if (strcmp (argv[i], "--verbose") == 0 || +	      strcmp (argv[i], "-v") == 0) +	    verbose = true; +	  else +	    runlevels = rc_strlist_add (runlevels, argv[i]); +	} + +      if (! runlevels) +	runlevels = rc_get_runlevels (); + +      STRLIST_FOREACH (services, service, i) +	{ +	  char **in = NULL; +	  bool inone = false; + +	  STRLIST_FOREACH (runlevels, runlevel, j) +	    { +	      if (rc_service_in_runlevel (service, runlevel)) +		{ +		  in = rc_strlist_add (in, runlevel); +		  inone = true; +		} +	      else +		{ +		  char buffer[PATH_MAX]; +		  memset (buffer, ' ', strlen (runlevel)); +		  buffer[strlen (runlevel)] = 0; +		  in = rc_strlist_add (in, buffer); +		} +	    } + +	  if (! inone && ! verbose) +	    continue; + +	  printf (" %20s |", service); +	  STRLIST_FOREACH (in, runlevel, j) +	   printf (" %s", runlevel); +	  printf ("\n"); +	} +      +      return (EXIT_SUCCESS); +    } + +  if (geteuid () != 0) +    eerrorx ("%s: must be root to add or delete services from runlevels", +	     applet); + +  if (! (service = argv[2])) +    eerrorx ("%s: no service specified", applet); + +  if (strcmp (argv[1], "add") == 0 || +      strcmp (argv[1], "-a") == 0) +    { +      if (! service)  +	eerrorx ("%s: no service specified", applet); +      if (! rc_service_exists (service)) +	eerrorx ("%s: service `%s' does not exist", applet, service); + +      if (argc < 4) +	add (rc_get_runlevel (), service); + +      for (i = 3; i < argc; i++) +	add (argv[i], service); + +      return (EXIT_SUCCESS); +    } + +  if (strcmp (argv[1], "delete") == 0 || +      strcmp (argv[1], "del") == 0 || +      strcmp (argv[1], "-d") == 0) +    { +      for (i = 3; i < argc; i++) +	runlevels = rc_strlist_add (runlevels, argv[i]); + +      if (! runlevels) +	runlevels = rc_strlist_add (runlevels, rc_get_runlevel ());  + +      STRLIST_FOREACH (runlevels, runlevel, i) +	{ +	  if (rc_service_in_runlevel (service, runlevel)) +	    { +	      if (rc_service_delete (runlevel, service)) +		einfo ("%s removed from runlevel %s", service, runlevel); +	      else +		eerror ("%s: failed to remove service `%s' from runlevel `%s': %s", +			applet, service, runlevel, strerror (errno)); +	    } +	} + +      return (EXIT_SUCCESS); +    } + +  eerrorx ("%s: unknown command `%s'", applet, argv[1]); +} diff --git a/src/rc.c b/src/rc.c new file mode 100644 index 00000000..b9b4c82e --- /dev/null +++ b/src/rc.c @@ -0,0 +1,1174 @@ +/* +   rc.c +   rc - manager for init scripts which control the startup, shutdown +   and the running of daemons on a Gentoo system. + +   Also a multicall binary for various commands that can be used in shell +   scripts to query service state, mark service state and provide the +   Gentoo einfo family of informational functions. +   +   Copyright 2007 Gentoo Foundation +   Released under the GPLv2 +   */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/utsname.h> +#include <sys/wait.h> +#include <errno.h> +#include <ctype.h> +#include <libgen.h> +#include <limits.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> + +#include "einfo.h" +#include "rc.h" +#include "rc-misc.h" +#include "rc-plugin.h" +#include "strlist.h" + +#define INITSH 			RC_LIBDIR "sh/init.sh" +#define HALTSH			RC_INITDIR "halt.sh" + +#define RC_SVCDIR_STARTING	RC_SVCDIR "starting/" +#define RC_SVCDIR_INACTIVE	RC_SVCDIR "inactive/" +#define RC_SVCDIR_STARTED	RC_SVCDIR "started/" +#define RC_SVCDIR_COLDPLUGGED	RC_SVCDIR "coldplugged/" + +#define INTERACTIVE		RC_SVCDIR "interactive" + +#define DEVBOOT			"/dev/.rcboot" + +/* Cleanup anything in main */ +#define CHAR_FREE(_item) \ + if (_item) \ +{ \ +  free (_item); \ +  _item = NULL; \ +} + +extern char **environ; + +static char **env = NULL; +static char **newenv = NULL; +static char **coldplugged_services; +static char **stop_services = NULL; +static char **start_services = NULL; +static rc_depinfo_t *deptree = NULL; +static char **types = NULL; +static char *mycmd = NULL; +static char *myarg = NULL; +static char *tmp = NULL; +static char *applet = NULL; + +struct termios *termios_orig; + +static void cleanup (void) +{ +  rc_plugin_unload (); + +  if (termios_orig) +    { +      tcsetattr (STDIN_FILENO, TCSANOW, termios_orig); +      free (termios_orig); +    } + +  if (env) +    rc_strlist_free (env); +  if (newenv) +    rc_strlist_free (newenv); +  if (coldplugged_services) +    rc_strlist_free (coldplugged_services); +  if (stop_services) +    rc_strlist_free (stop_services); +  if (start_services) +    rc_strlist_free (start_services); +  if (deptree) +    rc_free_deptree (deptree); +  if (types) +    rc_strlist_free (types); +  if (mycmd) +    free (mycmd); +  if (myarg) +    free (myarg); + +  /* Clean runlevel start, stop markers */ +  if (rc_is_dir (RC_SVCDIR "softscripts.new")) +    rc_rm_dir (RC_SVCDIR "softscripts.new", true); +  if (rc_is_dir (RC_SVCDIR "softscripts.old")) +    rc_rm_dir (RC_SVCDIR "softscripts.old", true); +} + +static int do_e (int argc, char **argv) +{ +  int retval = EXIT_SUCCESS; +  int i; +  int l = 0; +  char *message = NULL; +  char *p; +  char *fmt = NULL; + +  if (strcmp (applet, "eend") == 0 || +      strcmp (applet, "ewend") == 0 || +      strcmp (applet, "veend") == 0 || +      strcmp (applet, "vweend") == 0) +    { +      if (argc > 0) +	{ +	  errno = 0; +	  retval = strtol (argv[0], NULL, 0); +	  if (errno != 0) +	    retval = EXIT_FAILURE; +	  else +	    { +	      argc--; +	      argv++; +	    } +	} +      else +	retval = EXIT_FAILURE; +   } + +  if (argc > 0) +    { +      for (i = 0; i < argc; i++) +	l += strlen (argv[i]) + 1; + +      message = rc_xmalloc (l); +      p = message; + +      for (i = 0; i < argc; i++) +	{	 +	  if (i > 0) +	    *p++ = ' '; +	  memcpy (p, argv[i], strlen (argv[i])); +	  p += strlen (argv[i]); +	} +      *p = 0; +    } + +  if (message) +    fmt = strdup ("%s"); + +  if (strcmp (applet, "einfo") == 0)  +    einfo (fmt, message); +  else if (strcmp (applet, "einfon") == 0) +    einfon (fmt, message); +  else if (strcmp (applet, "ewarn") == 0)  +    ewarn (fmt, message); +  else if (strcmp (applet, "ewarnn") == 0) +    ewarnn (fmt, message); +  else if (strcmp (applet, "eerror") == 0) +    { +      eerror (fmt, message); +      retval = 1; +    } +  else if (strcmp (applet, "eerrorn") == 0) +    { +      eerrorn (fmt, message); +      retval = 1; +    } +  else if (strcmp (applet, "ebegin") == 0) +    ebegin (fmt, message); +  else if (strcmp (applet, "eend") == 0) +    eend (retval, fmt, message); +  else if (strcmp (applet, "ewend") == 0) +    ewend (retval, fmt, message); +  else if (strcmp (applet, "veinfo") == 0)  +    veinfo (fmt, message); +  else if (strcmp (applet, "veinfon") == 0) +    veinfon (fmt, message); +  else if (strcmp (applet, "vewarn") == 0)  +    vewarn (fmt, message); +  else if (strcmp (applet, "vewarnn") == 0) +    vewarnn (fmt, message); +  else if (strcmp (applet, "vebegin") == 0) +    vebegin (fmt, message); +  else if (strcmp (applet, "veend") == 0) +    veend (retval, fmt, message); +  else if (strcmp (applet, "vewend") == 0) +    vewend (retval, fmt, message); +  else if (strcmp (applet, "eindent") == 0) +    eindent (); +  else if (strcmp (applet, "eoutdent") == 0) +    eoutdent (); +  else if (strcmp (applet, "veindent") == 0) +    veindent (); +  else if (strcmp (applet, "veoutdent") == 0) +    veoutdent (); +  else if (strcmp (applet, "eflush") == 0) +    eflush (); +  else +    { +      eerror ("%s: unknown applet", applet); +      retval = EXIT_FAILURE; +    } + +  if (fmt) +    free (fmt); +  if (message) +    free (message); +  return (retval); +} + +static int do_service (int argc, char **argv) +{ +  bool ok = false; + +  if (argc < 1 || ! argv[0] || strlen (argv[0]) == 0) +    eerrorx ("%s: no service specified", applet); + +  if (strcmp (applet, "service_started") == 0) +    ok = rc_service_state (argv[0], rc_service_started); +  else if (strcmp (applet, "service_stopped") == 0) +    ok = rc_service_state (argv[0], rc_service_stopped); +  else if (strcmp (applet, "service_inactive") == 0) +    ok = rc_service_state (argv[0], rc_service_inactive); +  else if (strcmp (applet, "service_starting") == 0) +    ok = rc_service_state (argv[0], rc_service_starting); +  else if (strcmp (applet, "service_stopping") == 0) +    ok = rc_service_state (argv[0], rc_service_stopping); +  else if (strcmp (applet, "service_coldplugged") == 0) +    ok = rc_service_state (argv[0], rc_service_coldplugged); +  else if (strcmp (applet, "service_wasinactive") == 0) +    ok = rc_service_state (argv[0], rc_service_wasinactive); +  else if (strcmp (applet, "service_started_daemon") == 0) +    { +      int idx = 0; +      if (argc > 2) +	sscanf (argv[2], "%d", &idx); +      exit (rc_service_started_daemon (argv[0], argv[1], idx) +	    ? 0 : 1); +    } +  else +    eerrorx ("%s: unknown applet", applet); + +  return (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} + +static int do_mark_service (int argc, char **argv) +{ +  bool ok = false; +  char *svcname = getenv ("SVCNAME"); + +  if (argc < 1 || ! argv[0] || strlen (argv[0]) == 0) +    eerrorx ("%s: no service specified", applet); + +  if (strcmp (applet, "mark_service_started") == 0) +    ok = rc_mark_service (argv[0], rc_service_started); +  else if (strcmp (applet, "mark_service_stopped") == 0) +    ok = rc_mark_service (argv[0], rc_service_stopped); +  else if (strcmp (applet, "mark_service_inactive") == 0) +    ok = rc_mark_service (argv[0], rc_service_inactive); +  else if (strcmp (applet, "mark_service_starting") == 0) +    ok = rc_mark_service (argv[0], rc_service_starting); +  else if (strcmp (applet, "mark_service_stopping") == 0) +    ok = rc_mark_service (argv[0], rc_service_stopping); +  else if (strcmp (applet, "mark_service_coldplugged") == 0) +    ok = rc_mark_service (argv[0], rc_service_coldplugged); +  else +    eerrorx ("%s: unknown applet", applet); + +  /* If we're marking ourselves then we need to inform our parent runscript +     process so they do not mark us based on our exit code */ +  if (ok && svcname && strcmp (svcname, argv[0]) == 0) +    { +      char *runscript_pid = getenv ("RC_RUNSCRIPT_PID"); +      char *mtime; +      pid_t pid = 0; +      int l; + +      if (runscript_pid && sscanf (runscript_pid, "%d", &pid) == 1) +	if (kill (pid, SIGHUP) != 0) +	  eerror ("%s: failed to signal parent %d: %s", +		  applet, pid, strerror (errno)); + +      /* Remove the exclsive time test. This ensures that it's not +	 in control as well */ +      l = strlen (RC_SVCDIR "exclusive") + +       strlen (svcname) + +       strlen (runscript_pid) + +       4; +      mtime = rc_xmalloc (l); +      snprintf (mtime, l, RC_SVCDIR "exclusive/%s.%s", +		svcname, runscript_pid); +      if (rc_exists (mtime) && unlink (mtime) != 0) +	eerror ("%s: unlink: %s", applet, strerror (errno)); +      free (mtime); +    } + +  return (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} + +static int do_options (int argc, char **argv) +{ +  bool ok = false; +  char *service = getenv ("SVCNAME"); + +  if (! service) +    eerrorx ("%s: no service specified", applet); + +  if (argc < 1 || ! argv[0] || strlen (argv[0]) == 0) +    eerrorx ("%s: no option specified", applet); + +  if (strcmp (applet, "get_options") == 0) +    { +      char buffer[1024]; +      memset (buffer, 0, 1024); +      ok = rc_get_service_option (service, argv[0], buffer); +      if (ok) +	printf ("%s", buffer); +    } +  else if (strcmp (applet, "save_options") == 0) +    ok = rc_set_service_option (service, argv[0], argv[1]); +  else +    eerrorx ("%s: unknown applet", applet); + +  return (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} + +static char read_key (bool block) +{ +  struct termios termios; +  char c = 0; + +  if (! isatty (STDIN_FILENO)) +    return (false); + +  /* Now save our terminal settings. We need to restore them at exit as we +     will be changing it for non-blocking reads for Interactive */ +  if (! termios_orig) +    { +      termios_orig = rc_xmalloc (sizeof (struct termios)); +      tcgetattr (STDIN_FILENO, termios_orig); +    } + +  tcgetattr (STDIN_FILENO, &termios); +  termios.c_lflag &= ~(ICANON | ECHO); +  if (block) +    termios.c_cc[VMIN] = 1; +  else +    { +      termios.c_cc[VMIN] = 0; +      termios.c_cc[VTIME] = 0; +    } +  tcsetattr (STDIN_FILENO, TCSANOW, &termios); + +  read (STDIN_FILENO, &c, 1); + +  tcsetattr (STDIN_FILENO, TCSANOW, termios_orig); + +  return (c); +} + +static bool want_interactive (void) +{ +  char c = read_key (false); +  return ((c == 'I' || c == 'i') ? true : false); +} + +static void mark_interactive (void) +{ +  FILE *fp = fopen (INTERACTIVE, "w"); +  if (fp) +    fclose (fp); +} + +static void sulogin (bool cont) +{ +#ifdef __linux__ +  if (cont) +    { +      int status = 0; +      pid_t pid = fork(); + +      if (pid == -1) +	eerrorx ("%s: fork: %s", applet, strerror (errno)); +      if (pid == 0) +	{ +	  newenv = rc_filter_env (); +	  mycmd = rc_xstrdup ("/sbin/sulogin"); +	  myarg = rc_xstrdup (getenv ("CONSOLE")); +	  execle (mycmd, mycmd, myarg, NULL, newenv); +	  eerrorx ("%s: unable to exec `/sbin/sulogin': %s", applet, strerror (errno)); +	} +      waitpid (pid, &status, 0); +    } +  else +    { + +      newenv = rc_filter_env (); +      mycmd = rc_xstrdup ("/sbin/sulogin"); +      myarg = rc_xstrdup (getenv ("CONSOLE")); +      execle (mycmd, mycmd, myarg, NULL, newenv); +      eerrorx ("%s: unable to exec `/sbin/sulogin': %s", applet, strerror (errno)); +    } +#else +  /* Appease gcc */ +  cont = cont; +  exit (EXIT_SUCCESS); +#endif +} + +static void set_ksoftlevel (const char *runlevel) +{ +  FILE *fp; + +  if (! runlevel || +      strcmp (runlevel, RC_LEVEL_BOOT) == 0 || +      strcmp (runlevel, RC_LEVEL_SINGLE) == 0 || +      strcmp (runlevel, RC_LEVEL_SYSINIT) == 0) +    { +      if (rc_exists (RC_SVCDIR "ksoftlevel") && +	  unlink (RC_SVCDIR "ksoftlevel") != 0) +	eerror ("unlink `%s': %s", RC_SVCDIR "ksoftlevel", strerror (errno)); +      return; +    } + +  if (! (fp = fopen (RC_SVCDIR "ksoftlevel", "w"))) +    { +      eerror ("fopen `%s': %s", RC_SVCDIR "ksoftlevel", strerror (errno)); +      return; +    } +   +  fprintf (fp, "%s", runlevel); +  fclose (fp); +} + +static void wait_for_services () +{ +  int status = 0; +  struct timeval tv; +  while (wait (&status) != -1); + +  /* Wait for a little bit to flush our ebuffer */ +  tv.tv_usec = 50000; +  tv.tv_sec = 0; +  select (0, NULL, NULL, NULL, &tv); +} + +int main (int argc, char **argv) +{ +  char *RUNLEVEL = NULL; +  char *PREVLEVEL = NULL; +  char *runlevel = NULL; +  char *newlevel = NULL; +  char *service = NULL; +  char **deporder = NULL; +  int i = 0; +  int j = 0; +  bool going_down = false; +  bool interactive = false; +  int depoptions = RC_DEP_STRICT | RC_DEP_TRACE; +  char ksoftbuffer [PATH_MAX]; + +  if (argv[0]) +    applet = basename (argv[0]); + +  if (! applet) +    eerrorx ("arguments required"); + +  argc--; +  argv++; + +  /* Handle multicall stuff */ +  if (applet[0] == 'e' || (applet[0] == 'v' && applet[1] == 'e')) +    exit (do_e (argc, argv)); + +  if (strncmp (applet, "service_", strlen ("service_")) == 0) +    exit (do_service (argc, argv)); + +  if (strcmp (applet, "get_options") == 0 || +      strcmp (applet, "save_options") == 0) +    exit (do_options (argc, argv)); + +  if (strncmp (applet, "mark_service_", strlen ("mark_service_")) == 0) +    exit (do_mark_service (argc, argv)); + +  if (strcmp (applet, "is_runlevel_start") == 0) +    exit (rc_runlevel_starting () ? 0 : 1); +  else if (strcmp (applet, "is_runlevel_stop") == 0) +    exit (rc_runlevel_stopping () ? 0 : 1); +  else if (strcmp (applet, "color_terminal") == 0) +    exit (colour_terminal () ? 0 : 1); + +  if (strcmp (applet, "rc" ) != 0) +    eerrorx ("%s: unknown applet", applet); + +  /* OK, so we really are the main RC process +     Only root should be able to run us */ +  if (geteuid () != 0) +    eerrorx ("%s: root access required", applet); + +  atexit (cleanup); +  newlevel = argv[0]; + +  /* Ensure our environment is pure +     Also, add our configuration to it */ +  env = rc_filter_env (); +  env = rc_config_env (env); + +  if (env) +    { +      char *p; + +#ifdef __linux__ +      /* clearenv isn't portable, but there's no harm in using it +	 if we have it */ +      clearenv (); +#else +      char *var; +      /* No clearenv present here then. +	 We could manipulate environ directly ourselves, but it seems that +	 some kernels bitch about this according to the environ man pages +	 so we walk though environ and call unsetenv for each value. */ +      while (environ[0]) +	{ +	  tmp = rc_xstrdup (environ[0]); +	  p = tmp; +	  var = strsep (&p, "="); +	  unsetenv (var); +	  free (tmp); +	} +      tmp = NULL; +#endif + +      STRLIST_FOREACH (env, p, i) +       if (strcmp (p, "RC_SOFTLEVEL") != 0 && strcmp (p, "SOFTLEVEL") != 0) +	putenv (p); + +      /* We don't free our list as that would be null in environ */ +    } + +  /* Enable logging */ +  setenv ("RC_ELOG", "rc", 1); + +  interactive = rc_exists (INTERACTIVE); +  rc_plugin_load (); + +  /* RUNLEVEL is set by sysvinit as is a magic number +     RC_SOFTLEVEL is set by us and is the name for this magic number +     even though all our userland documentation refers to runlevel */ +  RUNLEVEL = getenv ("RUNLEVEL"); +  PREVLEVEL = getenv ("PREVLEVEL"); + +  if (RUNLEVEL && newlevel) +    { +      if (strcmp (RUNLEVEL, "S") == 0 || strcmp (RUNLEVEL, "1") == 0) +	{ +	  /* OK, we're either in runlevel 1 or single user mode */ +	  if (strcmp (newlevel, RC_LEVEL_SYSINIT) == 0) +	    { +	      struct utsname uts; +	      pid_t pid; +	      pid_t wpid; +	      int status = 0; +#ifdef __linux__ +	      FILE *fp; +#endif + +	      uname (&uts); + +	      printf ("\n"); +	      PEINFO_GOOD; +	      printf ("   Gentoo/%s; ", uts.sysname); +	      PEINFO_BRACKET; +	      printf ("http://www.gentoo.org/"); +	      PEINFO_NORMAL; +	      printf ("\n   Copyright 1999-2007 Gentoo Foundation; " +		      "Distributed under the GPLv2\n\n"); + +	      printf ("Press "); +	      PEINFO_GOOD; +	      printf ("I"); +	      PEINFO_NORMAL; +	      printf (" to enter interactive boot mode\n\n"); + +	      setenv ("RC_SOFTLEVEL", newlevel, 1); +	      rc_plugin_run (rc_hook_runlevel_start_in, newlevel); + +	      if ((pid = fork ()) == -1) +		eerrorx ("%s: fork: %s", applet, strerror (errno)); + +	      if (pid == 0) +		{ +		  mycmd = rc_xstrdup (INITSH); +		  execl (mycmd, mycmd, NULL); +		  eerrorx ("%s: unable to exec `" INITSH "': %s", +			   applet, strerror (errno)); +		} + +	      do +		{ +		  wpid = waitpid (pid, &status, 0); +		  if (wpid < 1) +		    eerror ("waitpid: %s", strerror (errno)); +		} while (! WIFEXITED (status) && ! WIFSIGNALED (status)); + +	      if (! WIFEXITED (status) || ! WEXITSTATUS (status) == 0) +		exit (EXIT_FAILURE); + +	      /* If we requested a softlevel, save it now */ +#ifdef __linux__ +	      set_ksoftlevel (NULL); + +	      if ((fp = fopen ("/proc/cmdline", "r"))) +		{ +		  char buffer[RC_LINEBUFFER]; +		  char *soft; + +		  memset (buffer, 0, sizeof (buffer)); +		  if (fgets (buffer, RC_LINEBUFFER, fp) && +		      (soft = strstr (buffer, "softlevel="))) +		    {  +		      i = soft - buffer; +		      if (i  == 0 || buffer[i - 1] == ' ') +			{ +			  char *level; + +			  /* Trim the trailing carriage return if present */ +			  i = strlen (buffer) - 1; +			  if (buffer[i] == '\n') +			    buffer[i] = 0; + +			  soft += strlen ("softlevel="); +			  level = strsep (&soft, " "); +			  set_ksoftlevel (level); +			} +		    } +		  fclose (fp); +		} +#endif +	      rc_plugin_run (rc_hook_runlevel_start_out, newlevel); + +	      if (want_interactive ()) +		mark_interactive (); + +	      exit (EXIT_SUCCESS); +	    } + +#ifdef __linux__ +	  /* Parse the inittab file so we can work out the level to telinit */ +	  if (strcmp (newlevel, RC_LEVEL_BOOT) != 0 && +	      strcmp (newlevel, RC_LEVEL_SINGLE) != 0) +	    { +	      char **inittab = rc_get_list (NULL, "/etc/inittab"); +	      char *line; +	      char *p; +	      char *token; +	      char lvl[2] = {0, 0}; + +	      STRLIST_FOREACH (inittab, line, i) +		{ +		  p = line; +		  token = strsep (&p, ":"); +		  if (! token || token[0] != 'l') +		    continue; + +		  if ((token = strsep (&p, ":")) == NULL) +		    continue; + +		  /* Snag the level */ +		  lvl[0] = token[0]; + +		  /* The name is spaced after this */ +		  if ((token = strsep (&p, " ")) == NULL) +		    continue; + +		  if ((token = strsep (&p, " ")) == NULL) +		    continue; +		   +		  if (strcmp (token, newlevel) == 0) +		    break; +		} +	      rc_strlist_free (inittab); +	       +	      /* We have a level, so telinit into it */ +	      if (lvl[0] == 0) +		{ +		  eerrorx ("%s: couldn't find a runlevel called `%s'", +			  applet, newlevel); +		} +	      else +		{ +		  mycmd = rc_xstrdup ("/sbin/telinit"); +		  myarg = rc_xstrdup (lvl); +		  execl (mycmd, mycmd, myarg, NULL); +		  eerrorx ("%s: unable to exec `/sbin/telinit': %s", +			   applet, strerror (errno)); +		} +	    }  +#endif +	} +    } + +  /* Check we're in the runlevel requested, ie from +     rc single +     rc shutdown +     rc reboot +     */ +  if (newlevel) +    { +      if (myarg) +	{ +	  free (myarg); +	  myarg = NULL; +	} + +      if (strcmp (newlevel, RC_LEVEL_SINGLE) == 0) +	{ +	  if (! RUNLEVEL || +	      (strcmp (RUNLEVEL, "S") != 0 && +	       strcmp (RUNLEVEL, "1") != 0)) +	    { +	      /* Remember the current runlevel for when we come back */ +	      set_ksoftlevel (runlevel); +#ifdef __linux__ +	      mycmd = rc_xstrdup ("/sbin/telinit"); +	      myarg = rc_xstrdup ("S"); +	      execl (mycmd, mycmd, myarg, NULL); +	      eerrorx ("%s: unable to exec `/%s': %s", +		       mycmd, applet, strerror (errno)); +#else +	      if (kill (1, SIGTERM) != 0) +		eerrorx ("%s: unable to send SIGTERM to init (pid 1): %s", +			 applet, strerror (errno)); +	      exit (EXIT_SUCCESS); +#endif +	    } +	} +      else if (strcmp (newlevel, RC_LEVEL_REBOOT) == 0) +	{ +	  if (! RUNLEVEL || +	      strcmp (RUNLEVEL, "6") != 0) +	    { +	      mycmd = rc_xstrdup ("/sbin/shutdown"); +	      myarg = rc_xstrdup ("-r"); +	      tmp = rc_xstrdup ("now"); +	      execl (mycmd, mycmd, myarg, tmp, NULL); +	      eerrorx ("%s: unable to exec `%s': %s", +		       mycmd, applet, strerror (errno)); +	    } +	} +      else if (strcmp (newlevel, RC_LEVEL_SHUTDOWN) == 0) +	{ +	  if (! RUNLEVEL || +	      strcmp (RUNLEVEL, "0") != 0) +	    { +	      mycmd = rc_xstrdup ("/sbin/shutdown"); +#ifdef __linux__ +	      myarg = rc_xstrdup ("-h"); +#else +	      myarg = rc_xstrdup ("-p"); +#endif +	      tmp = rc_xstrdup ("now"); +	      execl (mycmd, mycmd, myarg, tmp, NULL); +	      eerrorx ("%s: unable to exec `%s': %s", +		       mycmd, applet, strerror (errno)); +	    } +	} +    } + +  /* Export our current softlevel */ +  runlevel = rc_get_runlevel (); + +  /* If we're in the default runlevel and ksoftlevel exists, we should use +     that instead */ +  if (newlevel && +      rc_exists (RC_SVCDIR "ksoftlevel") && +      strcmp (newlevel, RC_LEVEL_DEFAULT) == 0) +    { +      /* We should only use ksoftlevel if we were in single user mode +	 If not, we need to erase ksoftlevel now. */ +      if (PREVLEVEL && +	  (strcmp (PREVLEVEL, "1") == 0 || +	   strcmp (PREVLEVEL, "S") == 0 || +	   strcmp (PREVLEVEL, "N") == 0)) +	{ +	  FILE *fp; + +	  if (! (fp = fopen (RC_SVCDIR "ksoftlevel", "r"))) +	    eerror ("fopen `%s': %s", RC_SVCDIR "ksoftlevel", +		    strerror (errno)); +	  else +	    { +	      if (fgets (ksoftbuffer, sizeof (ksoftbuffer), fp)) +		{ +		  i = strlen (ksoftbuffer) - 1; +		  if (ksoftbuffer[i] == '\n') +		    ksoftbuffer[i] = 0; +		  newlevel = ksoftbuffer; +		} +	      fclose (fp); +	    } +	} +      else +	set_ksoftlevel (NULL); +    } + +  if (newlevel && +      (strcmp (newlevel, RC_LEVEL_REBOOT) == 0 || +       strcmp (newlevel, RC_LEVEL_SHUTDOWN) == 0 || +       strcmp (newlevel, RC_LEVEL_SINGLE) == 0)) +    { +      going_down = true; +      rc_set_runlevel (newlevel); +      setenv ("RC_SOFTLEVEL", newlevel, 1); +      rc_plugin_run (rc_hook_runlevel_stop_in, newlevel); +    } +  else +    { +      rc_plugin_run (rc_hook_runlevel_stop_in, runlevel); +    } + +  /* Check if runlevel is valid if we're changing */ +  if (newlevel && strcmp (runlevel, newlevel) != 0 && ! going_down) +    { +      tmp = rc_strcatpaths (RC_RUNLEVELDIR, newlevel, NULL); +      if (! rc_is_dir (tmp)) +	eerrorx ("%s: is not a valid runlevel", newlevel); +      CHAR_FREE (tmp); +    } + +  /* Load our deptree now */ +  if ((deptree = rc_load_deptree ()) == NULL) +    eerrorx ("failed to load deptree"); + +  /* Clean the failed services state dir now */ +  if (rc_is_dir (RC_SVCDIR "failed")) +    rc_rm_dir (RC_SVCDIR "failed", false); + +  mkdir (RC_SVCDIR "/softscripts.new", 0755); + +#ifdef __linux__ +  /* udev likes to start services before we're ready when it does +     its coldplugging thing. runscript knows when we're not ready so it +     stores a list of coldplugged services in DEVBOOT for us to pick up +     here when we are ready for them */ +  if (rc_is_dir (DEVBOOT)) +    { +      start_services = rc_ls_dir (NULL, DEVBOOT, RC_LS_INITD); +      rc_rm_dir (DEVBOOT, true); + +      STRLIST_FOREACH (start_services, service, i) +       if (rc_allow_plug (service)) +	 rc_mark_service (service, rc_service_coldplugged); +      /* We need to dump this list now. +	 This may seem redunant, but only Linux needs this and saves on +	 code bloat. */ +      rc_strlist_free (start_services); +      start_services = NULL; +    } +#else +  /* BSD's on the other hand populate /dev automagically and use devd. +     The only downside of this approach and ours is that we have to hard code +     the device node to the init script to simulate the coldplug into +     runlevel for our dependency tree to work. */ +  if (newlevel && strcmp (newlevel, RC_LEVEL_BOOT) == 0 && +      (strcmp (runlevel, RC_LEVEL_SINGLE) == 0 || +       strcmp (runlevel, RC_LEVEL_SYSINIT) == 0) && +      rc_is_env ("RC_COLDPLUG", "yes")) +    { +      /* The net interfaces are easy - they're all in net /dev/net :) */ +      start_services = rc_ls_dir (NULL, "/dev/net", 0); +      STRLIST_FOREACH (start_services, service, i) +	{ +	  j = (strlen ("net.") + strlen (service) + 1); +	  tmp = rc_xmalloc (sizeof (char *) * j); +	  snprintf (tmp, j, "net.%s", service); +          if (rc_service_exists (tmp) && rc_allow_plug (tmp)) +            rc_mark_service (tmp, rc_service_coldplugged); +	  CHAR_FREE (tmp); +	} +      rc_strlist_free (start_services); + +      /* The mice are a little more tricky. +         If we coldplug anything else, we'll probably do it here. */ +      start_services = rc_ls_dir (NULL, "/dev", 0); +      STRLIST_FOREACH (start_services, service, i) +	{ +	  if (strncmp (service, "psm", 3) == 0 || +	      strncmp (service, "ums", 3) == 0) +	    { +	      char *p = service + 3; +	      if (p && isdigit (*p)) +		{ +		  j = (strlen ("moused.") + strlen (service) + 1); +		  tmp = rc_xmalloc (sizeof (char *) * j); +		  snprintf (tmp, j, "moused.%s", service); +		  if (rc_service_exists (tmp) && rc_allow_plug (tmp)) +		    rc_mark_service (tmp, rc_service_coldplugged); +		  CHAR_FREE (tmp); +		} +	    } +	} +      rc_strlist_free (start_services); +      start_services = NULL; +    } +#endif + +  /* Build a list of all services to stop and then work out the +     correct order for stopping them */ +  stop_services = rc_ls_dir (stop_services, RC_SVCDIR_STARTING, RC_LS_INITD); +  stop_services = rc_ls_dir (stop_services, RC_SVCDIR_INACTIVE, RC_LS_INITD); +  stop_services = rc_ls_dir (stop_services, RC_SVCDIR_STARTED, RC_LS_INITD); + +  types = rc_strlist_add (NULL, "ineed"); +  types = rc_strlist_add (types, "iuse"); +  types = rc_strlist_add (types, "iafter"); +  deporder = rc_get_depends (deptree, types, stop_services, +			     runlevel, depoptions); +  rc_strlist_free (stop_services); +  rc_strlist_free (types); +  stop_services = deporder; +  deporder = NULL; +  types = NULL; +  rc_strlist_reverse (stop_services); + +  /* Load our list of coldplugged services */ +  coldplugged_services = rc_ls_dir (coldplugged_services, +				 RC_SVCDIR_COLDPLUGGED, RC_LS_INITD); + +  /* Load our start services now. +     We have different rules dependent on runlevel. */ +  if (newlevel && strcmp (newlevel, RC_LEVEL_BOOT) == 0) +    { +      if (coldplugged_services) +	{ +	  einfon ("Device initiated services:"); +	  STRLIST_FOREACH (coldplugged_services, service, i) +	    { +	      printf (" %s", service); +	      start_services = rc_strlist_add (start_services, service); +	    } +	  printf ("\n"); +	} +      tmp = rc_strcatpaths (RC_RUNLEVELDIR, newlevel ? newlevel : runlevel, NULL); +      start_services = rc_ls_dir (start_services, tmp, RC_LS_INITD); +      CHAR_FREE (tmp); +    } +  else +    { +      /* Store our list of coldplugged services */ +      coldplugged_services = rc_ls_dir (coldplugged_services, RC_SVCDIR_COLDPLUGGED, +					RC_LS_INITD); +      if (strcmp (newlevel ? newlevel : runlevel, RC_LEVEL_SINGLE) != 0 && +	  strcmp (newlevel ? newlevel : runlevel, RC_LEVEL_SHUTDOWN) != 0 && +	  strcmp (newlevel ? newlevel : runlevel, RC_LEVEL_REBOOT) != 0) +	{ +	  /* We need to include the boot runlevel services if we're not in it */ +	  start_services = rc_ls_dir (start_services, RC_RUNLEVELDIR RC_LEVEL_BOOT, +				      RC_LS_INITD); +	  STRLIST_FOREACH (coldplugged_services, service, i) +	   start_services = rc_strlist_add (start_services, service); + +	  tmp = rc_strcatpaths (RC_RUNLEVELDIR, +				newlevel ? newlevel : runlevel, NULL); +	  start_services = rc_ls_dir (start_services, tmp, RC_LS_INITD); +	  CHAR_FREE (tmp); +	} +    } + +  /* Save out softlevel now */ +  if (going_down) +    rc_set_runlevel (newlevel); + +  types = rc_strlist_add (NULL, "needsme"); +  types = rc_strlist_add (types, "usesme"); +  /* Now stop the services that shouldn't be running */ +  STRLIST_FOREACH (stop_services, service, i) +    { +      bool found = false; +      char *conf = NULL; +      char **stopdeps = NULL; +      char *svc1 = NULL; +      char *svc2 = NULL; +      int k; + +      if (rc_service_state (service, rc_service_stopped)) +	continue; + +      /* We always stop the service when in these runlevels */ +      if (going_down) +	{ +	  rc_stop_service (service); +	  continue; +	} + +      /* If we're in the start list then don't bother stopping us */ +      STRLIST_FOREACH (start_services, svc1, j) +       if (strcmp (svc1, service) == 0) +	 { +	   found = true; +	   break; +	 } + +      /* Unless we would use a different config file */ +      if (found) +	{ +	  if (! newlevel) +	    continue; +	   +	  tmp = rc_xmalloc (strlen (service) + strlen (runlevel) + 2); +	  sprintf (tmp, "%s.%s", service, runlevel); +	  conf = rc_strcatpaths (RC_CONFDIR, tmp, NULL); +	  found = rc_exists (conf); +	  CHAR_FREE (conf); +	  CHAR_FREE (tmp); +	  if (! found) +	    { +	      tmp = rc_xmalloc (strlen (service) + strlen (newlevel) + 2); +	      sprintf (tmp, "%s.%s", service, newlevel); +	      conf = rc_strcatpaths (RC_CONFDIR, tmp, NULL); +	      found = rc_exists (conf); +	      CHAR_FREE (conf); +	      CHAR_FREE (tmp); +	      if (!found) +		continue; +	    } +	} +      else +	/* Allow coldplugged services not to be in the runlevels list */ +	{ +	  if (rc_service_state (service, rc_service_coldplugged)) +	    continue; +	} + +      /* We got this far! Or last check is to see if any any service that +	 going to be started depends on us */ +      stopdeps = rc_strlist_add (stopdeps, service); +      deporder = rc_get_depends (deptree, types, stopdeps, +				 runlevel, RC_DEP_STRICT); +      rc_strlist_free (stopdeps); +      stopdeps = NULL; +      found = false; +      STRLIST_FOREACH (deporder, svc1, j) +	{ +	  STRLIST_FOREACH (start_services, svc2, k) +	   if (strcmp (svc1, svc2) == 0) +	     { +	       found = true; +	       break; +	     } +	  if (found) +	    break; +	} +      rc_strlist_free (deporder); +      deporder = NULL; + +      /* After all that we can finally stop the blighter! */ +      if (! found) +	rc_stop_service (service); +    } +  rc_strlist_free (types); +  types = NULL; + +  /* Wait for our services to finish */ +  if (rc_is_env ("RC_PARALLEL_STARTUP", "yes")) +    wait_for_services (); + +  /* Notify the plugins we have finished */ +  rc_plugin_run (rc_hook_runlevel_stop_out, runlevel); + +  rmdir (RC_SVCDIR "/softscripts.new"); + +  /* Store the new runlevel */ +  if (newlevel) +    { +      rc_set_runlevel (newlevel); +      runlevel = newlevel; +      setenv ("RC_SOFTLEVEL", runlevel, 1); +    } + +  /* Run the halt script if needed */ +  if (strcmp (runlevel, RC_LEVEL_SHUTDOWN) == 0 || +      strcmp (runlevel, RC_LEVEL_REBOOT) == 0) +    { +      mycmd = rc_xstrdup (HALTSH); +      myarg = rc_xstrdup (runlevel); +      execl (mycmd, mycmd, myarg, NULL); +      eerrorx ("%s: unable to exec `%s': %s", +	       applet, HALTSH, strerror (errno)); +    } + +  /* Single user is done now */ +  if (strcmp (runlevel, RC_LEVEL_SINGLE) == 0) +    { +      if (rc_exists (INTERACTIVE)) +	unlink (INTERACTIVE); +      sulogin (false); +    } + +  mkdir (RC_SVCDIR "/softscripts.old", 0755); +  rc_plugin_run (rc_hook_runlevel_start_in, runlevel); + +  /* Re-add our coldplugged services if they stopped */ +  STRLIST_FOREACH (coldplugged_services, service, i) +   rc_mark_service (service, rc_service_coldplugged); + +  /* Order the services to start */ +  types = rc_strlist_add (NULL, "ineed"); +  types = rc_strlist_add (types, "iuse"); +  types = rc_strlist_add (types, "iafter"); +  deporder = rc_get_depends (deptree, types, start_services, +			     runlevel, depoptions); +  rc_strlist_free (types); +  types = NULL; +  rc_strlist_free (start_services); +  start_services = deporder; +  deporder = NULL; + +  STRLIST_FOREACH (start_services, service, i) +    { +      if (rc_service_state (service, rc_service_stopped)) +	{ +	  if (! interactive) +	    interactive = want_interactive (); + +	  if (interactive) +	    { +interactive_retry: +	      printf ("\n"); +	      einfo ("About to start the service %s", service); +	      eindent (); +	      einfo ("1) Start the service\t\t2) Skip the service"); +	      einfo ("3) Continue boot process\t\t4) Exit to shell"); +	      eoutdent (); +interactive_option: +	      switch (read_key (true)) +		{ +		case '1': break; +		case '2': continue; +		case '3': interactive = false; break; +		case '4': sulogin (true); goto interactive_retry; +		default: goto interactive_option; +		} +	    } +	  rc_start_service (service); +	} +    } + +  /* Wait for our services to finish */ +  if (rc_is_env ("RC_PARALLEL_STARTUP", "yes")) +    wait_for_services (); + +  rc_plugin_run (rc_hook_runlevel_start_out, runlevel); + +  /* Store our interactive status for boot */ +  if (interactive && strcmp (runlevel, RC_LEVEL_BOOT) == 0) +    mark_interactive (); +  else +    { +      if (rc_exists (INTERACTIVE)) +	unlink (INTERACTIVE); +    } + +  return (EXIT_SUCCESS); +} + diff --git a/src/rc.h b/src/rc.h new file mode 100644 index 00000000..3ba7cc56 --- /dev/null +++ b/src/rc.h @@ -0,0 +1,180 @@ +/* +   rc.h  +   Header file for external applications to get RC information. +   Copyright 2007 Gentoo Foundation +   Released under the GPLv2 +   */ + +#ifndef __RC_H__ +#define __RC_H__ + +#include <sys/types.h> +#include <stdbool.h> + +/* Special level names */ +#define RC_LEVEL_SYSINIT	"sysinit" +#define RC_LEVEL_BOOT		"boot" +#define RC_LEVEL_SINGLE		"single" +#define RC_LEVEL_SHUTDOWN	"shutdown" +#define RC_LEVEL_REBOOT		"reboot" +#define RC_LEVEL_DEFAULT	"default" + +typedef enum +{ +  rc_service_started, +  rc_service_stopped, +  rc_service_starting, +  rc_service_stopping, +  rc_service_inactive, +  rc_service_wasinactive, +  rc_service_coldplugged, +  rc_service_failed, +  rc_service_scheduled, +  rc_service_crashed +} rc_service_state_t; + +char *rc_resolve_service (const char *service); +bool rc_service_exists (const char *service); +bool rc_service_in_runlevel (const char *service, const char *runlevel); +bool rc_service_state (const char *service, rc_service_state_t state); +bool rc_mark_service (const char *service, rc_service_state_t state); +pid_t rc_stop_service (const char *service); +pid_t rc_start_service (const char *service); +void rc_schedule_start_service (const char *service, +				const char *service_to_start); +char **rc_services_scheduled_by (const char *service); +void rc_schedule_clear (const char *service); +bool rc_wait_service (const char *service); +bool rc_get_service_option (const char *service, const char *option, +			    char *value); +bool rc_set_service_option (const char *service, const char *option, +			    const char *value); +void rc_set_service_daemon (const char *service, const char *exec, +			    const char *name, const char *pidfile, +			    bool started); +bool rc_service_started_daemon (const char *service, const char *exec, +				int indx); + +bool rc_allow_plug (char *service); + +char *rc_get_runlevel (void); +void rc_set_runlevel (const char *runlevel); +bool rc_runlevel_exists (const char *runlevel); +char **rc_get_runlevels (void); +bool rc_runlevel_starting (void); +bool rc_runlevel_stopping (void); +bool rc_service_add (const char *runlevel, const char *service); +bool rc_service_delete (const char *runlevel, const char *service); +char **rc_services_in_runlevel (const char *runlevel); +char **rc_services_in_state (rc_service_state_t state); +char **rc_services_scheduled (const char *service); + +/* Find pids based on criteria - free the pointer returned after use */ +pid_t *rc_find_pids (const char *exec, const char *cmd, +		     uid_t uid, pid_t pid); +/* Checks that all daemons started with start-stop-daemon by the service +   are still running. If so, return false otherwise true. +   You should check that the service has been started before calling this. */ +bool rc_service_daemons_crashed (const char *service); + +/* Dependency tree structs and functions. */ +typedef struct rc_deptype +{ +  char *type; +  char **services; +  struct rc_deptype *next; +} rc_deptype_t; + +typedef struct rc_depinfo +{ +  char *service; +  rc_deptype_t *depends; +  struct rc_depinfo *next; +} rc_depinfo_t; + + +/* Options for rc_dep_depends and rc_order_services. +   When changing runlevels, you should use RC_DEP_START and RC_DEP_STOP for +   the start and stop lists as we tweak the provided services for this. */ +#define RC_DEP_TRACE  	0x01 +#define RC_DEP_STRICT	0x02 +#define RC_DEP_START    0x04 +#define RC_DEP_STOP     0x08 + +int rc_update_deptree (bool force); +rc_depinfo_t *rc_load_deptree (void); +rc_depinfo_t *rc_get_depinfo (rc_depinfo_t *deptree, const char *service); +rc_deptype_t *rc_get_deptype (rc_depinfo_t *depinfo, const char *type); +char **rc_get_depends (rc_depinfo_t *deptree, char **types, +		       char **services, const char *runlevel, int options); +/* List all the services that should be started, in order, the the +   given runlevel, including sysinit and boot services where +   approriate. +   If reboot, shutdown or single are given then we list all the services +   we that we need to shutdown in order. */ +char **rc_order_services (rc_depinfo_t *deptree, const char *runlevel, +			  int options); + +void rc_free_deptree (rc_depinfo_t *deptree); + +/* Plugin handler +   For each plugin loaded we will call it's _name_hook with the below +   enum and either the runlevel name or service name. For example +   int _splash_hook (rc_hook_t hook, const char *name); +   Plugins are called when rc does something. This does not indicate an +   end result and the plugin should use the above functions to query things +   like service status. */ +typedef enum +{ +  rc_hook_runlevel_stop_in = 1, +  rc_hook_runlevel_stop_out, +  rc_hook_runlevel_start_in, +  rc_hook_runlevel_start_out, +  rc_hook_service_stop_in, +  rc_hook_service_stop_out, +  rc_hook_service_start_in, +  rc_hook_service_start_out +} rc_hook_t; + +/* RC utility functions. +   Although not directly related to RC in general, they are used by RC +   itself and the supporting applications. */ +void *rc_xcalloc (size_t n, size_t size); +void *rc_xmalloc (size_t size); +void *rc_xrealloc (void *ptr, size_t size); +char *rc_xstrdup (const char *str); + +/* Concat paths adding '/' if needed. */ +char *rc_strcatpaths (const char *path1, const char *paths, ...); + +bool rc_is_env (const char *variable, const char *value); +bool rc_exists (const char *pathname); +bool rc_is_file (const char *pathname); +bool rc_is_link (const char *pathname); +bool rc_is_dir (const char *pathname); +bool rc_is_exec (const char *pathname); + +#define RC_LS_INITD	0x01 +char **rc_ls_dir (char **list, const char *dir, int options); + +bool rc_rm_dir (const char *pathname, bool top); + +/* Config file functions */ +char **rc_get_list (char **list, const char *file); +char **rc_get_config (char **list, const char *file); +char *rc_get_config_entry (char **list, const char *entry); + +/* Make an environment list which filters out all unwanted values +   and loads it up with our RC config */ +char **rc_filter_env (void); +char **rc_config_env (char **env); + +/* Handy functions for dealing with string arrays of char ** */ +char **rc_strlist_add (char **list, const char *item); +char **rc_strlist_addsort (char **list, const char *item); +char **rc_strlist_addsortc (char **list, const char *item); +char **rc_strlist_delete (char **list, const char *item); +void rc_strlist_reverse (char **list); +void rc_strlist_free (char **list); + +#endif diff --git a/src/runscript.c b/src/runscript.c new file mode 100644 index 00000000..bca1195b --- /dev/null +++ b/src/runscript.c @@ -0,0 +1,1097 @@ +/* + * runscript.c + * Handle launching of Gentoo init scripts. + * + * Copyright 1999-2007 Gentoo Foundation + * Distributed under the terms of the GNU General Public License v2 + */ + +#include <sys/types.h> +#include <sys/signal.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <dlfcn.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifndef __linux__ +#include <libgen.h> +#endif + +#include "einfo.h" +#include "rc.h" +#include "rc-misc.h" +#include "rc-plugin.h" +#include "strlist.h" + +#define RCSCRIPT_HELP	RC_LIBDIR "/sh/rc-help.sh" +#define SELINUX_LIB	RC_LIBDIR "/runscript_selinux.so" + +static char *applet = NULL; +static char *exclusive = NULL; +static char *mtime_test = NULL; +static rc_depinfo_t *deptree = NULL; +static char **services = NULL; +static char **svclist = NULL; +static char **tmplist = NULL; +static char **providelist = NULL; +static char **types = NULL; +static char **restart_services = NULL; +static char **need_services = NULL; +static char **env = NULL; +static char *mycmd = NULL; +static char *myarg1 = NULL; +static char *myarg2 = NULL; +static char *tmp = NULL; +static char *softlevel = NULL; +static bool sighup = false; +static char *ibsave = NULL; +static bool in_background = false; +static rc_hook_t hook_out = 0; + +extern char **environ; + +#ifdef __linux__ +static void (*selinux_run_init_old) (void); +static void (*selinux_run_init_new) (int argc, char **argv); + +void setup_selinux (int argc, char **argv); +#endif + +#ifdef __linux__ +void setup_selinux (int argc, char **argv) +{ +  void *lib_handle = NULL; + +  lib_handle = dlopen (SELINUX_LIB, RTLD_NOW | RTLD_GLOBAL); +  if (lib_handle) +    { +      /* FIXME: the below code generates the warning +	 ISO C forbids assignment between function pointer and 'void *' +	 which sucks ass +	 http://www.opengroup.org/onlinepubs/009695399/functions/dlsym.html */ +      selinux_run_init_old = dlsym (lib_handle, "selinux_runscript"); +      selinux_run_init_new = dlsym (lib_handle, "selinux_runscript2"); + +      /* Use new run_init if it rc_exists, else fall back to old */ +      if (selinux_run_init_new) +	selinux_run_init_new (argc, argv); +      else if (selinux_run_init_old) +	selinux_run_init_old (); +      else +	/* This shouldnt happen... probably corrupt lib */ +	eerrorx ("run_init is missing from runscript_selinux.so!"); +    } +} +#endif + +static void handle_signal (int sig) +{ +  pid_t pid; +  int status; +  int serrno = errno; + +  switch (sig) +    { +    case SIGHUP: +      sighup = true; +      break; + +    case SIGCHLD: +      do +	{ +	  pid = waitpid (-1, &status, WNOHANG); +	  if (pid < 0) +	    { +	      if (errno != ECHILD) +		eerror ("waitpid: %s", strerror (errno)); +	      return; +	    } +	} while (! WIFEXITED (status) && ! WIFSIGNALED (status)); +      break; + +    case SIGINT: +    case SIGTERM: +    case SIGQUIT: +      eerrorx ("%s: caught signal %d, aborting", applet, sig); + +    default: +      eerror ("%s: caught unknown signal %d", applet, sig); +    } + +  /* Restore errno */ +  errno = serrno; +} + +static time_t get_mtime (const char *pathname, bool follow_link) +{ +  struct stat buf; +  int retval; + +  if (! pathname) +    return (0); + +  retval = follow_link ? stat (pathname, &buf) : lstat (pathname, &buf); +  if (retval == 0) +    return (buf.st_mtime); + +  errno = 0; +  return (0); +} + +static bool in_control () +{ +  char *path; +  time_t mtime; +  const char *tests[] = { "starting", "started", "stopping", +    "inactive", "wasinactive", NULL }; +  int i = 0; + +  if (sighup) +    return (false); + +  if (mtime_test == NULL || ! rc_exists (mtime_test)) +    return (false); + +  if (rc_service_state (applet, rc_service_stopped)) +    return (false); + +  if ((mtime = get_mtime (mtime_test, false)) == 0) +    return (false); + +  while (tests[i]) +    { +      path = rc_strcatpaths (RC_SVCDIR, tests[i], applet, NULL); +      if (rc_exists (path)) +	{ +	  int m = get_mtime (path, false); +	  if (mtime < m && m != 0) +	    { +	      free (path); +	      return (false); +	    } +	} +      free (path); +      i++; +    } + +  return (true); +} + +static void uncoldplug (char *service) +{ +  char *cold = rc_strcatpaths (RC_SVCDIR "coldplugged", basename (service), NULL); +  if (rc_exists (cold) && unlink (cold) != 0) +    eerror ("%s: unlink `%s': %s", applet, cold, strerror (errno)); +  free (cold); +} + +static void cleanup (void) +{ +  /* Flush our buffered output if any */ +  eflush (); + +  if (hook_out) +    rc_plugin_run (hook_out, applet); +  rc_plugin_unload (); + +  if (deptree) +    rc_free_deptree (deptree); +  if (services) +    rc_strlist_free (services); +  if (types) +    rc_strlist_free (types); +  if (svclist) +    rc_strlist_free (svclist); +  if (providelist) +    rc_strlist_free (providelist); +  if (restart_services) +    rc_strlist_free (restart_services); +  if (need_services) +    rc_strlist_free (need_services); +  if (mycmd) +    free (mycmd); +  if (myarg1) +    free (myarg1); +  if (myarg2) +    free (myarg2); +  if (ibsave) +    free (ibsave); + +  if (in_control ()) +    { +      if (rc_service_state (applet, rc_service_starting)) +	{ +	  if (rc_service_state (applet, rc_service_wasinactive)) +	    rc_mark_service (applet, rc_service_inactive); +	  else +	    rc_mark_service (applet, rc_service_stopped); +	} +      else if (rc_service_state (applet, rc_service_stopping)) +	{ +	  /* If the we're shutting down, do it cleanly */ +	  if ((softlevel && rc_runlevel_stopping () && +	       (strcmp (softlevel, RC_LEVEL_SHUTDOWN) == 0 || +		strcmp (softlevel, RC_LEVEL_REBOOT) == 0)) || +	      ! rc_service_state (applet, rc_service_wasinactive))  +	    rc_mark_service (applet, rc_service_stopped); +	  else +	    rc_mark_service (applet, rc_service_inactive); +	} +      if (exclusive && rc_exists (exclusive)) +	unlink (exclusive); +    } + +  if (env) +    rc_strlist_free (env); + +  if (mtime_test) +    { +      unlink (mtime_test); +      free (mtime_test); +    } +  if (exclusive) +    free (exclusive); + +  if (applet) +    free (applet); +} + +static bool svc_exec (const char *service, const char *arg1, const char *arg2) +{ +  int status = 0; +  pid_t pid; + +  /* We need to disable our child signal handler now so we block +     until our script returns. */ +  signal (SIGCHLD, NULL); +  +  pid = fork(); + +  if (pid == -1) +    eerrorx ("%s: fork: %s", service, strerror (errno)); +  if (pid == 0) +    { +      mycmd = rc_xstrdup (service); +      myarg1 = rc_xstrdup (arg1); +      if (arg2) +	myarg2 = rc_xstrdup (arg2); + +      if (rc_exists (RC_SVCDIR "runscript.sh")) +	{ +	  execl (RC_SVCDIR "runscript.sh", mycmd, mycmd, myarg1, myarg2, NULL); +	  eerrorx ("%s: exec `" RC_SVCDIR "runscript.sh': %s", +		   service, strerror (errno)); +	} +      else +	{ +	  execl (RC_LIBDIR "sh/runscript.sh", mycmd, mycmd, myarg1, myarg2, NULL); +	  eerrorx ("%s: exec `" RC_LIBDIR "sh/runscript.sh': %s", +		   service, strerror (errno)); +	} +    } + +  do +    { +      if (waitpid (pid, &status, 0) < 0) +	{ +	  if (errno != ECHILD) +	    eerror ("waitpid: %s", strerror (errno)); +	  break; +	} +    } while (! WIFEXITED (status) && ! WIFSIGNALED (status)); + +  /* Done, so restore the signal handler */ +  signal (SIGCHLD, handle_signal); + +  if (WIFEXITED (status)) +    return (WEXITSTATUS (status) == 0 ? true : false); + +  return (false); +} + +static rc_service_state_t svc_status (const char *service) +{ +  char status[10]; +  int (*e) (const char *fmt, ...) = &einfo; + +  rc_service_state_t retval = rc_service_stopped; + +  if (rc_service_state (service, rc_service_stopping)) +    { +      snprintf (status, sizeof (status), "stopping"); +      e = &ewarn; +      retval = rc_service_stopping; +    } +  else if (rc_service_state (service, rc_service_starting)) +    { +      snprintf (status, sizeof (status), "starting"); +      e = &ewarn; +      retval = rc_service_starting; +    } +  else if (rc_service_state (service, rc_service_inactive)) +    { +      snprintf (status, sizeof (status), "inactive"); +      e = &ewarn; +      retval = rc_service_inactive; +    } +  else if (rc_service_state (service, rc_service_crashed)) +    { +      snprintf (status, sizeof (status), "crashed"); +      e = &eerror; +      retval = rc_service_crashed; +    } +  else if (rc_service_state (service, rc_service_started)) +    { +      snprintf (status, sizeof (status), "started"); +      retval = rc_service_started; +    } +  else +    snprintf (status, sizeof (status), "stopped"); + +  e ("status: %s", status); +  return (retval); +} + +static void make_exclusive (const char *service) +{ +  char *path; +  int i; + +  /* We create a fifo so that other services can wait until we complete */ +  if (! exclusive) +    exclusive = rc_strcatpaths (RC_SVCDIR, "exclusive", applet, NULL); + +  if (mkfifo (exclusive, 0600) != 0 && errno != EEXIST && +      (errno != EACCES || geteuid () == 0)) +    eerrorx ("%s: unable to create fifo `%s': %s", +	     applet, exclusive, strerror (errno)); + +  path = rc_strcatpaths (RC_SVCDIR, "exclusive", applet, NULL); +  i = strlen (path) + 16; +  mtime_test = rc_xmalloc (sizeof (char *) * i); +  snprintf (mtime_test, i, "%s.%d", path, getpid ()); +  free (path); + +  if (rc_exists (mtime_test) && unlink (mtime_test) != 0) +    { +      eerror ("%s: unlink `%s': %s", +	      applet, mtime_test, strerror (errno)); +      free (mtime_test); +      mtime_test = NULL; +      return; +    } + +  if (symlink (service, mtime_test) != 0) +    { +      eerror ("%s: symlink `%s' to `%s': %s", +	      applet, service, mtime_test, strerror (errno)); +      free (mtime_test); +      mtime_test = NULL; +    } +} + +static void unlink_mtime_test () +{ +  if (unlink (mtime_test) != 0) +    eerror ("%s: unlink `%s': %s", applet, mtime_test, strerror (errno)); +  free (mtime_test); +  mtime_test = NULL; +} + +static void get_started_services () +{ +  char *service; +  int i; + +  rc_strlist_free (tmplist); +  tmplist = rc_services_in_state (rc_service_inactive); + +  rc_strlist_free (restart_services); +  restart_services = rc_services_in_state (rc_service_started); + +  STRLIST_FOREACH (tmplist, service, i) +   restart_services = rc_strlist_addsort (restart_services, service); + +  rc_strlist_free (tmplist); +  tmplist = NULL; +} + +static void svc_start (const char *service, bool deps) +{ +  bool started; +  bool background = false; +  char *svc; +  char *svc2; +  int i; +  int j; +  int depoptions = RC_DEP_TRACE; + +  if (rc_is_env ("RC_STRICT_DEPEND", "yes")) +    depoptions |= RC_DEP_STRICT; + +  if (rc_is_env ("IN_HOTPLUG", "1") || in_background) +    { +      if (! rc_service_state (service, rc_service_inactive)) +	exit (EXIT_FAILURE); +      background = true; +    } + +  if (rc_service_state (service, rc_service_started)) +    ewarnx ("WARNING: %s has already been started", applet); +  else if (rc_service_state (service, rc_service_starting)) +    ewarnx ("WARNING: %s is already starting", applet); +  else if (rc_service_state (service, rc_service_stopping)) +    ewarnx ("WARNING: %s is stopping", applet); +  else if (rc_service_state (service, rc_service_inactive) && ! background) +    ewarnx ("WARNING: %s has already started, but is inactive", applet); + +  if (! rc_mark_service (service, rc_service_starting)) +    eerrorx ("ERROR: %s has been started by something else", applet); + +  make_exclusive (service); + +  if (deps) +    { +      if (! deptree && ((deptree = rc_load_deptree ()) == NULL)) +	eerrorx ("failed to load deptree"); + +      rc_strlist_free (types); +      types = rc_strlist_add (NULL, "broken"); +      rc_strlist_free (svclist); +      svclist = rc_strlist_add (NULL, applet); +      rc_strlist_free (services); +      services = rc_get_depends (deptree, types, svclist, softlevel, 0); +      if (services) +	{ +	  eerrorn ("ERROR: `%s' needs ", applet); +	  STRLIST_FOREACH (services, svc, i) +	    { +	      if (i > 0) +		fprintf (stderr, ", "); +	      fprintf (stderr, "%s", svc); +	    } +	  exit (EXIT_FAILURE); +	} +      rc_strlist_free (services); +      services = NULL; + +      rc_strlist_free (types); +      types = rc_strlist_add (NULL, "ineed"); +      rc_strlist_free (need_services); +      need_services = rc_get_depends (deptree, types, svclist, +				      softlevel, depoptions); +      types = rc_strlist_add (types, "iuse"); +      if (! rc_runlevel_starting ()) +	{ +	  services = rc_get_depends (deptree, types, svclist, +				     softlevel, depoptions); +	  STRLIST_FOREACH (services, svc, i) +	   if (rc_service_state (svc, rc_service_stopped)) +	     rc_start_service (svc); + +	  rc_strlist_free (services); +	} + +      /* Now wait for them to start */ +      types = rc_strlist_add (types, "iafter"); +      services = rc_get_depends (deptree, types, svclist, +				 softlevel, depoptions); + +      /* We use tmplist to hold our scheduled by list */ +      rc_strlist_free (tmplist); +      tmplist = NULL; + +      STRLIST_FOREACH (services, svc, i) +	{ +	  if (rc_service_state (svc, rc_service_started)) +	    continue; +	  if (! rc_wait_service (svc)) +	    { eerror ("%s: timed out waiting for %s", applet, svc); +	      system ("ps ax > /tmp/$SVCNAME.waiting"); } +	  if (rc_service_state (svc, rc_service_started)) +	    continue; + +	  STRLIST_FOREACH (need_services, svc2, j) +	   if (strcmp (svc, svc2) == 0) +	     { +	       if (rc_service_state (svc, rc_service_inactive) || +		   rc_service_state (svc, rc_service_wasinactive)) +		 tmplist = rc_strlist_add (tmplist, svc); +	       else +		 eerrorx ("ERROR: cannot start %s as %s would not start", +			  applet, svc); +	     } +	} + +      if (tmplist) +	{ +	  int n = 0; +	  int len = 0; +	  char *p; + +	  /* Set the state now, then unlink our exclusive so that +	     our scheduled list is preserved */ +	  rc_mark_service (service, rc_service_stopped); +	  unlink_mtime_test (); + +	  rc_strlist_free (types); +	  types = rc_strlist_add (NULL, "iprovide"); +	  STRLIST_FOREACH (tmplist, svc, i) +	    { +	      rc_schedule_start_service (svc, service); + +	      rc_strlist_free (svclist); +	      svclist = rc_strlist_add (NULL, svc); +	      rc_strlist_free (providelist); +	      providelist = rc_get_depends (deptree, types, svclist, +					    softlevel, depoptions); +	      STRLIST_FOREACH (providelist, svc2, j) +	       rc_schedule_start_service (svc2, service); + +	      len += strlen (svc) + 2; +	      n++; +	    } + +	  tmp = rc_xmalloc (sizeof (char *) * len + 5); +	  p = tmp; +	  STRLIST_FOREACH (tmplist, svc, i) +	    { +	      if (i > 1) +		{ +		  if (i == n - 1) +		    p += sprintf (p, " or "); +		  else +		    p += sprintf (p, ", "); +		} +	      p += sprintf (p, "%s", svc); +	    } +	  ewarnx ("WARNING: %s is scheduled to start when %s has started", +		  applet, tmp); +	} + +      rc_strlist_free (services); +      services = NULL; +      rc_strlist_free (types); +      types = NULL; +      rc_strlist_free (svclist); +      svclist = NULL; +    } + +  if (ibsave) +    setenv ("IN_BACKGROUND", ibsave, 1); +  rc_plugin_run (rc_hook_service_start_in, applet); +  hook_out = rc_hook_service_start_out; +  started = svc_exec (service, "start", NULL); +  if (ibsave) +    unsetenv ("IN_BACKGROUND"); + +  if (in_control ()) +    { +      if (! started) +	{ +	  if (rc_service_state (service, rc_service_wasinactive)) +	    rc_mark_service (service, rc_service_inactive); +	  else +	    { +	      rc_mark_service (service, rc_service_stopped); +	      if (rc_runlevel_starting ()) +		rc_mark_service (service, rc_service_failed); +	    } +	  eerrorx ("ERROR: %s failed to start", applet); +	} + +      rc_mark_service (service, rc_service_started); +      unlink_mtime_test (); + +      hook_out = 0; +      rc_plugin_run (rc_hook_service_start_out, applet); +    } +  else +    { +      if (rc_service_state (service, rc_service_inactive)) +	ewarn ("WARNING: %s has started, but is inactive", applet); +      else +	ewarn ("WARNING: %s not under our control, aborting", applet); +    } + +  /* Now start any scheduled services */ +  rc_strlist_free (services); +  services = rc_services_scheduled (service); +  STRLIST_FOREACH (services, svc, i) +   if (rc_service_state (svc, rc_service_stopped)) +     rc_start_service (svc); +  rc_strlist_free (services); +  services = NULL; + +  /* Do the same for any services we provide */ +  rc_strlist_free (types); +  types = rc_strlist_add (NULL, "iprovide"); +  rc_strlist_free (svclist); +  svclist = rc_strlist_add (NULL, applet); +  rc_strlist_free (tmplist); +  tmplist = rc_get_depends (deptree, types, svclist, softlevel, depoptions); + +  STRLIST_FOREACH (tmplist, svc2, j) +    { +      rc_strlist_free (services); +      services = rc_services_scheduled (svc2); +      STRLIST_FOREACH (services, svc, i) +       if (rc_service_state (svc, rc_service_stopped)) +	 rc_start_service (svc); +    } +} + +static void svc_stop (const char *service, bool deps) +{ +  bool stopped; + +  if (rc_runlevel_stopping () && +      rc_service_state (service, rc_service_failed)) +    exit (EXIT_FAILURE); + +  if (rc_is_env ("IN_HOTPLUG", "1") || in_background) +    if (! rc_service_state (service, rc_service_started)) +      exit (EXIT_FAILURE); + +  if (rc_service_state (service, rc_service_stopped)) +    ewarnx ("WARNING: %s is already stopped", applet); +  else if (rc_service_state (service, rc_service_stopping)) +    ewarnx ("WARNING: %s is already stopping", applet); + +  if (! rc_mark_service (service, rc_service_stopping)) +    eerrorx ("ERROR: %s has been stopped by something else", applet); + +  make_exclusive (service); + +  if (! rc_runlevel_stopping () && +    rc_service_in_runlevel (service, RC_LEVEL_BOOT)) +      ewarn ("WARNING: you are stopping a boot service"); + +  if (deps || ! rc_service_state (service, rc_service_wasinactive)) +    { +      int depoptions = RC_DEP_TRACE; +      char *svc; +      int i; +       +      if (rc_is_env ("RC_STRICT_DEPEND", "yes")) +	depoptions |= RC_DEP_STRICT; + +      if (! deptree && ((deptree = rc_load_deptree ()) == NULL)) +	eerrorx ("failed to load deptree"); + +      rc_strlist_free (types); +      types = rc_strlist_add (NULL, "needsme"); +      rc_strlist_free (svclist); +      svclist = rc_strlist_add (NULL, applet); +      rc_strlist_free (tmplist); +      tmplist = NULL; +      rc_strlist_free (services); +      services = rc_get_depends (deptree, types, svclist, +				 softlevel, depoptions); +      rc_strlist_reverse (services); +      STRLIST_FOREACH (services, svc, i) +	{ +	  if (rc_service_state (svc, rc_service_started) ||  +	      rc_service_state (svc, rc_service_inactive)) +	    { +	      rc_wait_service (svc); +	      if (rc_service_state (svc, rc_service_started) ||  +		  rc_service_state (svc, rc_service_inactive)) +		{ +		  rc_stop_service (svc); +		  tmplist = rc_strlist_add (tmplist, svc); +		} +	    } +	} +      rc_strlist_free (services); +      services = NULL; + +      STRLIST_FOREACH (tmplist, svc, i) +	{ +	  if (rc_service_state (svc, rc_service_stopped)) +	    continue; + +	  /* We used to loop 3 times here - maybe re-do this if needed */ +	  rc_wait_service (svc); +	  if (! rc_service_state (svc, rc_service_stopped)) +	    { +	      if (rc_runlevel_stopping ()) +		rc_mark_service (svc, rc_service_failed); +	      eerrorx ("ERROR:  cannot stop %s as %s is still up", +		       applet, svc); +	    } +	} +      rc_strlist_free (tmplist); +      tmplist = NULL; + +      /* We now wait for other services that may use us and are stopping +	 This is important when a runlevel stops */ +      types = rc_strlist_add (types, "usesme"); +      types = rc_strlist_add (types, "ibefore"); +      services = rc_get_depends (deptree, types, svclist, +				 softlevel, depoptions); +      STRLIST_FOREACH (services, svc, i) +	{ +	  if (rc_service_state (svc, rc_service_stopped)) +	    continue; +	  rc_wait_service (svc); +	} + +      rc_strlist_free (services); +      services = NULL; +      rc_strlist_free (types); +      types = NULL; +    } + +  if (ibsave) +    setenv ("IN_BACKGROUND", ibsave, 1); +  rc_plugin_run (rc_hook_service_stop_in, applet); +  hook_out = rc_hook_service_stop_out; +  stopped = svc_exec (service, "stop", NULL); +  if (ibsave) +    unsetenv ("IN_BACKGROUND"); + +  if (! in_control ()) +    ewarnx ("WARNING: %s not under our control, aborting", applet); + +  if (! stopped) +    { +      if (rc_service_state (service, rc_service_wasinactive)) +	rc_mark_service (service, rc_service_inactive); +      else +	rc_mark_service (service, rc_service_stopped); +      eerrorx ("ERROR: %s failed to stop", applet); +    } + +  if (in_background) +    rc_mark_service (service, rc_service_inactive); +  else +    rc_mark_service (service, rc_service_stopped); + +  unlink_mtime_test (); +  hook_out = 0; +  rc_plugin_run (rc_hook_service_stop_out, applet); +} + +static void svc_restart (const char *service, bool deps) +{ +  char *svc; +  int i; +  bool inactive = false; +  +  /* This is hairly and a better way needs to be found I think! +     The issue is this - openvpn need net and dns. net can restart +     dns via resolvconf, so you could have openvpn trying to restart dnsmasq +     which in turn is waiting on net which in turn is waiting on dnsmasq. +     The work around is for resolvconf to restart it's services with --nodeps +     which means just that. The downside is that there is a small window when +     our status is invalid. +     One workaround would be to introduce a new status, or status locking. */ +  if (! deps) +    { +      if (rc_service_state (service, rc_service_started) || +	  rc_service_state (service, rc_service_inactive)) +	svc_exec (service, "stop", "start"); +      else +	svc_exec (service, "start", NULL); +      return; +    } + +  if (! rc_service_state (service, rc_service_stopped)) +    { +      get_started_services (); +      svc_stop (service, deps); + +      /* Flush our buffered output if any */ +      eflush (); +    } + +  svc_start (service, deps); + +  inactive = rc_service_state (service, rc_service_inactive); +  if (! inactive) +    inactive = rc_service_state (service, rc_service_wasinactive); +   +  if (inactive || +      rc_service_state (service, rc_service_starting) || +      rc_service_state (service, rc_service_started)) +    { +      STRLIST_FOREACH (restart_services, svc, i) +	{ +	  if (rc_service_state (svc, rc_service_stopped)) +	    { +	      if (inactive) +		{ +		  rc_schedule_start_service (service, svc); +		  ewarn ("WARNING: %s is scheduled to started when %s has started", +			 svc, basename (service)); +		} +	      else +		rc_start_service (svc); +	    } +	} +    } +} + +int main (int argc, char **argv) +{ +  const char *service = argv[1]; +  int i; +  bool deps = true; +  bool doneone = false; +  char pid[16]; +  int retval; +  bool ifstarted = false; + +  applet = strdup (basename (service)); +  atexit (cleanup); + +  /* Show help if insufficient args */ +  if (argc < 3) +    { +      execl (RCSCRIPT_HELP, RCSCRIPT_HELP, service, NULL); +      eerrorx ("%s: failed to exec `" RCSCRIPT_HELP "': %s", +	       applet, strerror (errno)); +    } + +#ifdef __linux__ +  /* coldplug events can trigger init scripts, but we don't want to run them +     until after rc sysinit has completed so we punt them to the boot runlevel */ +  if (rc_exists ("/dev/.rcsysinit")) +    { +      eerror ("%s: cannot run until sysvinit completes", applet); +      if (mkdir ("/dev/.rcboot", 0755) != 0 && errno != EEXIST) +	eerrorx ("%s: mkdir `/dev/.rcboot': %s", applet, strerror (errno)); +      tmp = rc_strcatpaths ("/dev/.rcboot", applet, NULL); +      symlink (service, tmp); +      exit (EXIT_FAILURE); +    } +#endif + +  if ((softlevel = getenv ("RC_SOFTLEVEL")) == NULL) +    { +      /* Ensure our environment is pure +	 Also, add our configuration to it */ +      env = rc_filter_env (); +      env = rc_config_env (env); + +      if (env) +	{ +	  char *p; + +#ifdef __linux__ +	  /* clearenv isn't portable, but there's no harm in using it +	     if we have it */ +	  clearenv (); +#else +	  char *var; +	  /* No clearenv present here then. +	     We could manipulate environ directly ourselves, but it seems that +	     some kernels bitch about this according to the environ man pages +	     so we walk though environ and call unsetenv for each value. */ +	  while (environ[0]) +	    { +	      tmp = rc_xstrdup (environ[0]); +	      p = tmp; +	      var = strsep (&p, "="); +	      unsetenv (var); +	      free (tmp); +	    } +	  tmp = NULL; +#endif + +	  STRLIST_FOREACH (env, p, i) +	   putenv (p); + +	  /* We don't free our list as that would be null in environ */ +	} + +      softlevel = rc_get_runlevel (); + +      /* If not called from RC or another service then don't be parallel */ +      unsetenv ("RC_PARALLEL_STARTUP"); +    } + +  setenv ("RC_ELOG", service, 1); +  setenv ("SVCNAME", applet, 1); + +  /* Set an env var so that we always know our pid regardless of any +     subshells the init script may create so that our mark_service_* +     functions can always instruct us of this change */ +  snprintf (pid, sizeof (pid), "%d", (int) getpid ()); +  setenv ("RC_RUNSCRIPT_PID", pid, 1); + +  if (rc_is_env ("RC_PARALLEL_STARTUP", "yes")) +    { +      char ebname[PATH_MAX]; +      char *eb; + +      snprintf (ebname, sizeof (ebname), "%s.%s", applet, pid); +      eb = rc_strcatpaths (RC_SVCDIR "ebuffer", ebname, NULL); +      setenv ("RC_EBUFFER", eb, 1); +      free (eb); +    } + +  /* Save the IN_BACKGROUND env flag so it's ONLY passed to the service +     that is being called and not any dependents */ +  if (getenv ("IN_BACKGROUND")) +    { +      in_background = rc_is_env ("IN_BACKGROUND", "true"); +      ibsave = strdup (getenv ("IN_BACKGROUND")); +      unsetenv ("IN_BACKGROUND"); + +      /* Don't hang around */ +      if (in_background) +	setenv ("RC_PARALLEL_STARTUP", "yes", 1); +    } + +#ifdef __linux__ +  /* Ok, we are ready to go, so setup selinux if applicable */ +  setup_selinux (argc, argv); +#endif + +  /* Right then, parse any options there may be */ +  for (i = 2; i < argc; i++) +    { +      if (strlen (argv[i]) < 2 || argv[i][0] != '-' || argv[i][1] != '-') +	continue; + +      if (strcmp (argv[i], "--debug") == 0) +	setenv ("RC_DEBUG", "yes", 1); +      else if (strcmp (argv[i], "--help") == 0) +	{ +	  execl (RCSCRIPT_HELP, RCSCRIPT_HELP, service, NULL); +	  eerrorx ("%s: failed to exec `" RCSCRIPT_HELP "': %s", +		   applet, strerror (errno)); +	} +      else if (strcmp (argv[i],"--ifstarted") == 0) +	ifstarted = true; +      else if (strcmp (argv[i], "--nocolour") == 0 || +	       strcmp (argv[i], "--nocolor") == 0) +	setenv ("RC_NOCOLOR", "yes", 1); +      else if (strcmp (argv[i], "--nodeps") == 0) +	deps = false; +      else if (strcmp (argv[i], "--quiet") == 0) +	setenv ("RC_QUIET", "yes", 1); +      else if (strcmp (argv[i], "--verbose") == 0) +	setenv ("RC_VERBOSE", "yes", 1); +      else if (strcmp (argv[i], "--version") == 0) +	printf ("version me\n"); +      else +	eerror ("%s: unknown option `%s'", applet, argv[i]); +    } + +  if (ifstarted && ! rc_service_state (applet, rc_service_started)) +    { +      if (! rc_is_env("RC_QUIET", "yes")) +	eerror ("ERROR: %s is not started", applet); +      exit (EXIT_FAILURE); +    } + +  if (rc_is_env ("IN_HOTPLUG", "1")) +    { +      if (! rc_is_env ("RC_HOTPLUG", "yes") || ! rc_allow_plug (applet)) +	eerrorx ("%s: not allowed to be hotplugged", applet); +    } + +  /* Setup a signal handler */ +  signal (SIGHUP, handle_signal); +  signal (SIGINT, handle_signal); +  signal (SIGQUIT, handle_signal); +  signal (SIGTERM, handle_signal); +  signal (SIGCHLD, handle_signal); + +  /* Load our plugins */ +  rc_plugin_load (); + +  /* Now run each option */ +  retval = EXIT_SUCCESS; +  for (i = 2; i < argc; i++) +    { +      /* Abort on a sighup here */ +      if (sighup) +	exit (EXIT_FAILURE); + +      if (strlen (argv[i]) < 2 || +	  (argv[i][0] == '-' && argv[i][1] == '-')) +	continue; + +      /* Export the command we're running. +	 This is important as we stamp on the restart function now but +	 some start/stop routines still need to behave differently if +	 restarting. */ +      unsetenv ("RC_CMD"); +      setenv ("RC_CMD", argv[i], 1); +  +      doneone = true; +      if (strcmp (argv[i], "conditionalrestart") == 0 || +	  strcmp (argv[i], "condrestart") == 0) +	{ +	  if (rc_service_state (service, rc_service_started)) +	    svc_restart (service, deps); +	} +      else if (strcmp (argv[i], "restart") == 0) +	svc_restart (service, deps); +      else if (strcmp (argv[i], "start") == 0) +	svc_start (service, deps); +      else if (strcmp (argv[i], "status") == 0) +	{ +	  rc_service_state_t r = svc_status (service); +	  retval = (int) r; +	} +      else if (strcmp (argv[i], "stop") == 0) +	{ +	  if (in_background) +	    get_started_services (); +	  else if (! rc_runlevel_stopping ()) +	    uncoldplug (applet); + +	  svc_stop (service, deps); + +	  if (in_background && +	      rc_service_state (service, rc_service_inactive)) +	    { +	      char *svc; +	      int j; +	      STRLIST_FOREACH (restart_services, svc, j) +	       if (rc_service_state (svc, rc_service_stopped)) +		 rc_schedule_start_service (service, svc); +	    } +	} +      else if (strcmp (argv[i], "zap") == 0) +	{ +	  einfo ("Manually resetting %s to stopped state", applet); +	  rc_mark_service (applet, rc_service_stopped); +	  uncoldplug (applet); +	} +      else if (strcmp (argv[i], "help") == 0) +	{ +	  execl (RCSCRIPT_HELP, RCSCRIPT_HELP, service, "help", NULL); +	  eerrorx ("%s: failed to exec `" RCSCRIPT_HELP "': %s", +		   applet, strerror (errno)); +	} +      else +	svc_exec (service, argv[i], NULL); + +      /* Flush our buffered output if any */ +      eflush (); + +      /* We should ensure this list is empty after an action is done */ +      rc_strlist_free (restart_services); +      restart_services = NULL; +    } + +  if (! doneone) +    { +      execl (RCSCRIPT_HELP, RCSCRIPT_HELP, service, NULL); +      eerrorx ("%s: failed to exec `" RCSCRIPT_HELP "': %s", +	       applet, strerror (errno)); +    } + +  return (retval); +} diff --git a/src/splash.c b/src/splash.c new file mode 100644 index 00000000..3a4edfd9 --- /dev/null +++ b/src/splash.c @@ -0,0 +1,130 @@ +/* +   splash.c + +   Splash plugin for the Gentoo RC sytsem. +   splashutils needs to be re-written to support our new system. +   Until then, we provide this compatible module which calls the +   legacy bash scripts which is nasty. And slow. + +   For any themes that use scripts, such as the live-cd theme, +   they will have to source /sbin/splash-functions.sh themselves like so + +   if ! type splash >/dev/null 2>/dev/null ; then +      . /sbin/splash-functions.sh +   fi +    +   */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <rc.h> + +#ifndef LIBDIR +#  define LIBDIR "lib" +#endif + +#define SPLASH_CACHEDIR "/" LIBDIR "/splash/cache" + +#define SPLASH_CMD "bash -c 'export SOFTLEVEL='%s'; export BOOTLEVEL=${RC_BOOTLEVEL}; export DEFAULTLEVEL=${RC_DEFAULTLEVEL}; export svcdir=${RC_SVCDIR}; add_suffix() { echo \"$@\"; }; . /etc/init.d/functions.sh; . /sbin/splash-functions.sh; splash %s %s %s'" + +int _splash_hook (rc_hook_t hook, const char *name); + +static int _do_splash (const char *cmd, const char *arg1, const char *arg2) +{ +  char *c; +  int l; +  char *soft = getenv ("RC_SOFTLEVEL"); + +  if (! cmd || ! soft) +    return (-1); + +  l = strlen (SPLASH_CMD) + strlen (soft) + strlen (cmd); +  if (arg1) +    l += strlen (arg1); +  if (arg2) +    l += strlen (arg2); +  c = malloc (sizeof (char *) * l); +  if (! c) +    return (-1); + +  snprintf (c, l, SPLASH_CMD, +	    arg1 ? strcmp (arg1, RC_LEVEL_SYSINIT) == 0 ? RC_LEVEL_BOOT : soft : soft, +	    cmd, arg1 ? arg1 : "", arg2 ? arg2 : ""); +  l = system (c); +  free (c); +  return (l); +} + +int _splash_hook (rc_hook_t hook, const char *name) +{ +  switch (hook) +    { +    case rc_hook_runlevel_stop_in: +      if (strcmp (name, RC_LEVEL_SYSINIT) != 0) +	return (_do_splash ("rc_init", name, NULL)); +      break; +    case rc_hook_runlevel_start_out: +      if (strcmp (name, RC_LEVEL_SYSINIT) == 0) +	return (_do_splash ("rc_init", name, NULL)); +      else +	return (_do_splash ("rc_exit", name, NULL)); +    default: ; +    } + +  /* We don't care about splash unless we're changing runlevels */ +  if (! rc_runlevel_starting () &&  +      ! rc_runlevel_stopping ()) +    return (0); + +  switch (hook) +    { +    case rc_hook_service_stop_in: +      /* We need to stop localmount from unmounting our cache dir. +	 Luckily plugins can add to the unmount list. */ +      if (name && strcmp (name, "localmount") == 0) +	{ +	  char *umounts = getenv ("RC_NO_UMOUNTS"); +	  char *new; +	  int i = strlen (SPLASH_CACHEDIR) + 1; + +	  if (umounts) +	    i += strlen (umounts) + 1; + +	  new = malloc (sizeof (char *) * i); +	  if (new) +	    { +	      if (umounts) +		snprintf (new, i, "%s:%s", umounts, SPLASH_CACHEDIR); +	      else +		snprintf (new, i, "%s", SPLASH_CACHEDIR); +	    } + +	  /* We unsetenv first as some libc's leak memory if we overwrite +	     a var with a bigger value */ +	  if (umounts) +	    unsetenv ("RC_NO_UMOUNTS"); +	  setenv ("RC_NO_UMOUNTS", new, 1); + +	  free (new); +	} +      return (_do_splash ("svc_stop", name, NULL)); +    case rc_hook_service_stop_out: +      if (rc_service_state (name, rc_service_stopped)) +	return (_do_splash ("svc_stopped", name, "0")); +      else +	return (_do_splash ("svc_started", name, "1")); +    case rc_hook_service_start_in: +      return (_do_splash ("svc_start", name, NULL)); +    case rc_hook_service_start_out: +      if (rc_service_state (name, rc_service_stopped)) +	return (_do_splash ("svc_started", name, "1")); +      else +	return (_do_splash ("svc_started", name, "0")); +    default: ; +    } + +  return (0); +} diff --git a/src/start-stop-daemon.c b/src/start-stop-daemon.c new file mode 100644 index 00000000..e5dae783 --- /dev/null +++ b/src/start-stop-daemon.c @@ -0,0 +1,1047 @@ +/* +   start-stop-daemon +   Starts, stops, tests and signals daemons +   Copyright 2007 Gentoo Foundation +   Released under the GPLv2 + +   This is essentially a ground up re-write of Debians +   start-stop-daemon for cleaner code and to integrate into our RC +   system so we can monitor daemons a little. +   */ + +#define POLL_INTERVAL 20000 +#define START_WAIT    100000 + +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/termios.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <grp.h> +#include <pwd.h> +#include <signal.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef HAVE_PAM +#include <security/pam_appl.h> + +/* We are not supporting authentication conversations */ +static struct pam_conv conv = { NULL, NULL} ; +#endif + +#include "einfo.h" +#include "rc.h" +#include "rc-misc.h" +#include "strlist.h" + +typedef struct schedulelist +{ +  enum +    { +      schedule_timeout, +      schedule_signal, +      schedule_goto, +      schedule_forever +    } type; +  int value; +  struct schedulelist *gotolist; +  struct schedulelist *next; +} schedulelist_t; +static schedulelist_t *schedule; + +static char *progname; +static char *changeuser; +static char **newenv; + +extern char **environ; + +static void free_schedulelist (schedulelist_t **list) +{ +  schedulelist_t *here; +  schedulelist_t *next; + +  for (here = *list; here; here = next) +    { +      next = here->next; +      free (here); +    } + +  *list = NULL; +} + +static void cleanup (void) +{ +  if (changeuser) +    free (changeuser); + +  if (schedule) +    free_schedulelist (&schedule); + +  if (newenv) +    rc_strlist_free (newenv); +} + +static int parse_signal (const char *sig) +{ +  typedef struct signalpair +    { +      const char *name; +      int signal; +    } signalpair_t; + +  static const signalpair_t signallist[] = { +      { "ABRT",	SIGABRT	}, +      { "ALRM",	SIGALRM	}, +      { "FPE",	SIGFPE	}, +      { "HUP",	SIGHUP	}, +      { "ILL",	SIGILL	}, +      { "INT",	SIGINT	}, +      { "KILL",	SIGKILL	}, +      { "PIPE",	SIGPIPE	}, +      { "QUIT",	SIGQUIT	}, +      { "SEGV",	SIGSEGV	}, +      { "TERM",	SIGTERM	}, +      { "USR1",	SIGUSR1	}, +      { "USR2",	SIGUSR2	}, +      { "CHLD",	SIGCHLD	}, +      { "CONT",	SIGCONT	}, +      { "STOP",	SIGSTOP	}, +      { "TSTP",	SIGTSTP	}, +      { "TTIN",	SIGTTIN	}, +      { "TTOU",	SIGTTOU	} +  }; + +  unsigned int i = 0; +  char *s; + +  if (! sig || strlen (sig) == 0) +    return (-1); + +  if (sscanf (sig, "%u", &i) == 1) +    { +      if (i > 0 && i < sizeof (signallist) / sizeof (signallist[0])) +	return (i); +      eerrorx ("%s: `%s' is not a valid signal", progname, sig); +    } + +  if (strncmp (sig, "SIG", 3) == 0) +    s = (char *) sig + 3; +  else +    s = NULL; + +  for (i = 0; i < sizeof (signallist) / sizeof (signallist[0]); i++) +    if (strcmp (sig, signallist[i].name) == 0 || +	(s && strcmp (s, signallist[i].name) == 0)) +      return (signallist[i].signal); + +  eerrorx ("%s: `%s' is not a valid signal", progname, sig); +} + +static void parse_schedule_item (schedulelist_t *item, const char *string) +{ +  const char *after_hyph; +  int sig; + +  if (strcmp (string,"forever") == 0) +    item->type = schedule_forever; +  else if (isdigit (string[0])) +    { +      item->type = schedule_timeout; +      errno = 0; +      if (sscanf (string, "%d", &item->value) != 1) +	eerrorx ("%s: invalid timeout value in schedule `%s'", progname, +		 string); +    } +  else if ((after_hyph = string + (string[0] == '-')) && +	   ((sig = parse_signal (after_hyph)) != -1)) +    { +      item->type = schedule_signal; +      item->value = (int) sig; +    } +  else +    eerrorx ("%s: invalid schedule item `%s'", progname, string); +} + +static void parse_schedule (const char *string, int default_signal) +{ +  char buffer[20]; +  const char *slash; +  int count = 0; +  schedulelist_t *repeatat = NULL; +  ptrdiff_t len; +  schedulelist_t *next; + +  if (string) +    for (slash = string; *slash; slash++) +      if (*slash == '/') +	count++; + +  if (schedule) +    free_schedulelist (&schedule); + +  schedule = rc_xmalloc (sizeof (schedulelist_t)); +  schedule->gotolist = NULL; + +  if (count == 0) +    { +      schedule->type = schedule_signal; +      schedule->value = default_signal; +      schedule->next = rc_xmalloc (sizeof (schedulelist_t)); +      next = schedule->next; +      next->type = schedule_timeout; +      next->gotolist = NULL; +      if (string) +	{ +	  if (sscanf (string, "%d", &next->value) != 1) +	    eerrorx ("%s: invalid timeout value in schedule", progname); +	} +      else +	next->value = 5; +      next->next = NULL; + +      return; +    } + +  next = schedule; +  while (string != NULL) +    { +      if ((slash = strchr (string, '/'))) +	len = slash - string; +      else +	len = strlen (string); + +      if (len >= (ptrdiff_t) sizeof (buffer)) +	eerrorx ("%s: invalid schedule item, far too long", progname); + +      memcpy (buffer, string, len); +      buffer[len] = 0; +      string = slash ? slash + 1 : NULL; + +      parse_schedule_item (next, buffer); +      if (next->type == schedule_forever) +	{ +	  if (repeatat) +	    eerrorx ("%s: invalid schedule, `forever' appears more than once", +		     progname); + +	  repeatat = next; +	  continue; +	} + +      if (string) +	{ +	  next->next = rc_xmalloc (sizeof (schedulelist_t)); +	  next = next->next; +	  next->gotolist = NULL; +	} +    } + +  if (repeatat) +    { +      next->next = rc_xmalloc (sizeof (schedulelist_t)); +      next = next->next; +      next->type = schedule_goto; +      next->value = 0; +      next->gotolist = repeatat; +    } + +  next->next = NULL; +  return; +} + +static pid_t get_pid (const char *pidfile, bool quiet) +{ +  FILE *fp; +  pid_t pid; + +  if (! pidfile) +    return (-1); + +  if ((fp = fopen (pidfile, "r")) == NULL) +    { +      if (! quiet) +	eerror ("%s: fopen `%s': %s", progname, pidfile, strerror (errno)); +      return (-1); +    } + +  if (fscanf (fp, "%d", &pid) != 1) +    { +      if (! quiet) +	eerror ("%s: no pid found in `%s'", progname, pidfile); +      fclose (fp); +      return (-1); +    } +  fclose (fp); + +  return (pid); +} + +/* return number of processed killed, -1 on error */ +static int do_stop (const char *exec, const char *cmd, +		    const char *pidfile, uid_t uid,int sig, +		    bool quiet, bool verbose, bool test) +{ +  pid_t *pids;  +  bool killed; +  int nkilled = 0; +  pid_t pid = 0; +  int i; + +  if (pidfile) +    if ((pid = get_pid (pidfile, quiet)) == -1) +      return (quiet ? 0 : -1); + +  if ((pids = rc_find_pids (exec, cmd, uid, pid)) == NULL) +    return (0); + +  for (i = 0; pids[i]; i++) +    { +      if (test) +	{ +	  if (! quiet) +	    einfo ("Would send signal %d to PID %d", sig, pids[i]); +	  nkilled++; +	  continue; +	} + +      if (verbose) +	ebegin ("Sending signal %d to PID %d", sig, pids[i]); +      errno = 0; +      killed = (kill (pids[i], sig) == 0 || errno == ESRCH ? true : false); +      if (! killed) +	{ +	  if (! quiet) +	    eerror ("%s: failed to send signal %d to PID %d: %s", +		    progname, sig, pids[i], strerror (errno)); +	  if (verbose) +	    eend (1, NULL); +	  nkilled = -1; +	} +      else +	{ +	  if (verbose) +	    eend (0, NULL); +	  if (nkilled != -1) +	    nkilled++; +	} +    } + +  free (pids); +  return (nkilled); +} + +static int run_stop_schedule (const char *exec, const char *cmd, +			       const char *pidfile, uid_t uid, +			       bool quiet, bool verbose, bool test) +{ +  schedulelist_t *item = schedule; +  int nkilled = 0; +  int tkilled = 0; +  int nrunning = 0; +  struct timeval tv; +  struct timeval now; +  struct timeval stopat; + +  if (verbose) +    { +      if (pidfile) +	einfo ("Will stop PID in pidfile `%s'", pidfile); +      if (uid) +	einfo ("Will stop processes owned by UID %d", uid); +      if (exec) +	einfo ("Will stop processes of `%s'", exec); +      if (cmd) +	einfo ("Will stop processes called `%s'", cmd); +    } + +  while (item) +    { +      switch (item->type) +	{ +	case schedule_goto: +	  item = item->gotolist; +	  continue; + +	case schedule_signal: +	  nrunning = 0; +	  nkilled = do_stop (exec, cmd, pidfile, uid, item->value, +			     quiet, verbose, test); +	  if (nkilled == 0) +	    { +	      if (tkilled == 0) +		{ +		  if (! quiet) +		    eerror ("%s: no matching processes found", progname); +		} +	      return (tkilled); +	    } +	  else if (nkilled == -1) +	    return (0); + +	  tkilled += nkilled; +	  break; +	case schedule_timeout: +	  if (item->value < 1) +	    { +	      item = NULL; +	      break; +	    } + +	  if (gettimeofday (&stopat, NULL) != 0) +	    { +	      eerror ("%s: gettimeofday: %s", progname, strerror (errno)); +	      return (0); +	    } + +	  stopat.tv_sec += item->value; +	  while (1) +	    { +	      if ((nrunning = do_stop (exec, cmd, pidfile, +				       uid, 0, true, false, true)) == 0) +		return (true); + +	      tv.tv_sec = 0; +	      tv.tv_usec = POLL_INTERVAL; +	      if (select (0, 0, 0, 0, &tv) < 0) +		{ +		  if (errno == EINTR) +		    eerror ("%s: caught an interupt", progname); +		  else +		    eerror ("%s: select: %s", progname, strerror (errno)); +		  return (0); +		} + +	      if (gettimeofday (&now, NULL) != 0) +		{ +		  eerror ("%s: gettimeofday: %s", progname, strerror (errno)); +		  return (0); +		} +	      if (timercmp (&now, &stopat, >)) +		break; +	    } +	  break; + +	default: +	  eerror ("%s: invalid schedule item `%d'", progname, item->type); +	  return (0); +	} + +      if (item) +	item = item->next; +    } + +  if (test || (tkilled > 0 && nrunning == 0)) +    return (nkilled); + +  if (! quiet) +    { +      if (nrunning == 1) +	eerror ("%s: %d process refused to stop", progname, nrunning); +      else +	eerror ("%s: %d process(es) refused to stop", progname, nrunning); +    } + +  return (-nrunning); +} + +static void handle_signal (int sig) +{ +  int pid; +  int status; +  int serrno = errno; + +  switch (sig) +    { +    case SIGINT: +    case SIGTERM: +    case SIGQUIT: +      eerrorx ("%s: caught signal %d, aborting", progname, sig); + +    case SIGCHLD: +      while (1) +	{ +	  if ((pid = waitpid (-1, &status, WNOHANG)) < 0) +	    { +	      if (errno != ECHILD) +	        eerror ("%s: waitpid: %s", progname, strerror (errno)); +	      break; +	    } +	} +      break; +   +    default: +      eerror ("%s: caught unknown signal %d", progname, sig); +    } + +  /* Restore errno */ +  errno = serrno; +} + +int main (int argc, char **argv) +{ +  int devnull_fd = -1; + +#ifdef TIOCNOTTY +  int tty_fd = -1; +#endif +#ifdef HAVE_PAM +  pam_handle_t *pamh = NULL; +  int pamr; +#endif + +  static struct option longopts[] = { +      { "stop",         0, NULL, 'K'}, +      { "nicelevel",    1, NULL, 'N'}, +      { "retry",        1, NULL, 'R'}, +      { "start",        0, NULL, 'S'}, +      { "background",   0, NULL, 'b'}, +      { "chuid",        1, NULL, 'c'}, +      { "chdir",        1, NULL, 'd'}, +      { "group",        1, NULL, 'g'}, +      { "make-pidfile", 0, NULL, 'm'}, +      { "name",         1, NULL, 'n'}, +      { "oknodo",       0, NULL, 'o'}, +      { "pidfile",      1, NULL, 'p'}, +      { "quiet",        0, NULL, 'q'}, +      { "signal",       1, NULL, 's'}, +      { "test",         0, NULL, 't'}, +      { "user",         1, NULL, 'u'}, +      { "chroot",       1, NULL, 'r'}, +      { "verbose",      0, NULL, 'v'}, +      { "exec",         1, NULL, 'x'}, +      { "stdout",       1, NULL, '1'}, +      { "stderr",       1, NULL, '2'}, +      { NULL,           0, NULL, 0} +  }; +  int c; +  bool start = false; +  bool stop = false; +  bool oknodo = false; +  bool test = false; +  bool quiet = false; +  bool verbose = false; +  char *exec = NULL; +  char *cmd = NULL; +  char *pidfile = NULL; +  int sig = SIGTERM; +  uid_t uid = 0; +  int nicelevel = 0; +  bool background = false; +  bool makepidfile = false; +  uid_t ch_uid = 0; +  gid_t ch_gid = 0; +  char *ch_root = NULL; +  char *ch_dir = NULL; +  int tid = 0; +  char *redirect_stderr = NULL; +  char *redirect_stdout = NULL; +  int stdout_fd; +  int stderr_fd; +  pid_t pid; +  struct timeval tv; +  int i; +  char *svcname = getenv ("SVCNAME"); +  char *env; + +  progname = argv[0]; +  atexit (cleanup); + +  signal (SIGINT, handle_signal); +  signal (SIGQUIT, handle_signal); +  signal (SIGTERM, handle_signal); + +  while ((c = getopt_long (argc, argv, +			   "KN:R:Sbc:d:g:mn:op:qs:tu:r:vx:1:2:", +			   longopts, (int *) 0)) != -1) +    switch (c) +      { +      case 'K':  /* --stop */ +	stop = true; +	break; + +      case 'N':  /* --nice */ +	if (sscanf (optarg, "%d", &nicelevel) != 1) +	  eerrorx ("%s: invalid nice level `%s'", progname, optarg); +	break; + +      case 'R':  /* --retry <schedule>|<timeout> */ +	parse_schedule (optarg, sig); +	break; + +      case 'S':  /* --start */ +	start = true; +	break; + +      case 'b':  /* --background */ +	background = true; +	break; + +      case 'c':  /* --chuid <username>|<uid> */ +	/* we copy the string just in case we need the +	 * argument later. */ +	  { +	    char *p = optarg; +	    char *cu = strsep (&p, ":"); +	    changeuser = strdup (cu); +	    if (sscanf (cu, "%d", &tid) != 1) +	      { +		struct passwd *pw = getpwnam (cu); +		if (! pw) +		  eerrorx ("%s: user `%s' not found", progname, cu); +		ch_uid = pw->pw_uid; +	      } +	    else +	      ch_uid = tid; +	    if (p) +	      { +		char *cg = strsep (&p, ":"); +		if (sscanf (cg, "%d", &tid) != 1) +		  { +		    struct group *gr = getgrnam (cg); +		    if (! gr) +		      eerrorx ("%s: group `%s' not found", progname, cg); +		    ch_gid = gr->gr_gid; +		  } +		else +		  ch_gid = tid; +	      } +	  } +	break; + +      case 'd':  /* --chdir /new/dir */ +	ch_dir = optarg; +	break; + +      case 'g':  /* --group <group>|<gid> */ +	if (sscanf (optarg, "%d", &tid) != 1) +	  { +	    struct group *gr = getgrnam (optarg); +	    if (! gr) +	      eerrorx ("%s: group `%s' not found", progname, optarg); +	    ch_gid = gr->gr_gid; +	  } +	else +	  ch_gid = tid; +	break; + +      case 'm':  /* --make-pidfile */ +	makepidfile = true; +	break; + +      case 'n':  /* --name <process-name> */ +	cmd = optarg; +	break; + +      case 'o':  /* --oknodo */ +	oknodo = true; +	break; + +      case 'p':  /* --pidfile <pid-file> */ +	pidfile = optarg; +	break; + +      case 'q':  /* --quiet */ +	quiet = true; +	break; + +      case 's':  /* --signal <signal> */ +	sig = parse_signal (optarg); +	break; + +      case 't':  /* --test */ +	test = true; +	break; + +      case 'u':  /* --user <username>|<uid> */ +	if (sscanf (optarg, "%d", &tid) != 1) +	  { +	    struct passwd *pw = getpwnam (optarg); +	    if (! pw) +	      eerrorx ("%s: user `%s' not found", progname, optarg); +	    uid = pw->pw_uid; +	  } +	else +	  uid = tid; +	break; + +      case 'r':  /* --chroot /new/root */ +	ch_root = optarg; +	break; + +      case 'v':  /* --verbose */ +	verbose = true; +	break; + +      case 'x':  /* --exec <executable> */ +	exec = optarg; +	break; + +      case '1':   /* --stdout /path/to/stdout.lgfile */ +	redirect_stdout = optarg; +	break; + +      case '2':  /* --stderr /path/to/stderr.logfile */ +	redirect_stderr = optarg; +	break; + +      default: +	exit (EXIT_FAILURE); +      } + +  /* Respect RC as well as how we are called */ +  if (rc_is_env ("RC_QUIET", "yes") && ! verbose) +    quiet = true; + +  if (start == stop) +    eerrorx ("%s: need one of --start or --stop", progname); + +  if (start && ! exec) +    eerrorx ("%s: --start needs --exec", progname); + +  if (stop && ! exec && ! pidfile && ! cmd && ! uid) +    eerrorx ("%s: --stop needs --exec, --pidfile, --name or --user", progname); + +  if (makepidfile && ! pidfile) +    eerrorx ("%s: --make-pidfile is only relevant with --pidfile", progname); + +  if (background && ! start) +    eerrorx ("%s: --background is only relevant with --start", progname); + +  if ((redirect_stdout || redirect_stderr) && ! background) +    eerrorx ("%s: --stdout and --stderr are only relevant with --background", +	     progname); + +  argc -= optind; +  argv += optind; + +  /* Validate that the binary rc_exists if we are starting */ +  if (exec && start) +    { +      char *tmp; +      if (ch_root) +	tmp = rc_strcatpaths (ch_root, exec, NULL); +      else +	tmp = exec; +      if (! rc_is_file (tmp)) +	{ +	  eerror ("%s: %s does not exist", progname, tmp); +	  if (ch_root) +	    free (tmp); +	  exit (EXIT_FAILURE); +	} +      if (ch_root) +	free (tmp); +    } + +  if (stop) +    { +      int result; + +      if (! schedule) +	{ +	  if (test || oknodo) +	    parse_schedule ("0", sig); +	  else +	    parse_schedule (NULL, sig); +	} + +      result = run_stop_schedule (exec, cmd, pidfile, uid, quiet, verbose, test); +      if (test || oknodo) +	return (result > 0 ? EXIT_SUCCESS : EXIT_FAILURE); +      if (result < 1) +	exit (result == 0 ? EXIT_SUCCESS : EXIT_FAILURE); + +      if (pidfile && rc_is_file (pidfile)) +	unlink (pidfile); + +      if (svcname) +	rc_set_service_daemon (svcname, exec, cmd, pidfile, false); + +      exit (EXIT_SUCCESS); +    } + +  if (do_stop (exec, cmd, pidfile, uid, 0, true, false, true) > 0) +    eerrorx ("%s: %s is already running", progname, exec); + +  if (test) +    { +      if (quiet) +	exit (EXIT_SUCCESS); + +      einfon ("Would start %s", exec); +      while (argc-- > 0) +	printf("%s ", *argv++); +      printf ("\n"); +      eindent (); +      if (ch_uid != 0) +	einfo ("as user %d", ch_uid); +      if (ch_gid != 0) +	einfo ("as group %d", ch_gid); +      if (ch_root) +	einfo ("in root `%s'", ch_root); +      if (ch_dir) +	einfo ("in dir `%s'", ch_dir); +      if (nicelevel != 0) +	einfo ("with a priority of %d", nicelevel); +      eoutdent (); +      exit (EXIT_SUCCESS); +    } + +  /* Ensure this is unset, so if the daemon does /etc/init.d/foo +     Then we filter the environment accordingly */ +  unsetenv ("RC_SOFTLEVEL"); + +  if (verbose) +    { +      ebegin ("Detaching to start `%s'", exec); +      eindent (); +    } + +  if (background) +    signal (SIGCHLD, handle_signal); + +  *--argv = exec; +  if ((pid = fork ()) == -1) +    eerrorx ("%s: fork: %s", progname, strerror (errno)); + +  /* Child process - lets go! */ +  if (pid == 0) +    { +      pid_t mypid = getpid (); + +#ifdef TIOCNOTTY +      tty_fd = open("/dev/tty", O_RDWR); +#endif + +      devnull_fd = open("/dev/null", O_RDWR); + +      if (nicelevel) +	{ +	  if (setpriority (PRIO_PROCESS, mypid, nicelevel) == -1) +	    eerrorx ("%s: setpritory %d: %s", progname, nicelevel, +		     strerror(errno)); +	} + +      if (ch_root && chroot (ch_root) < 0) +	eerrorx ("%s: chroot `%s': %s", progname, ch_root, strerror (errno)); + +      if (ch_dir && chdir (ch_dir) < 0) +	eerrorx ("%s: chdir `%s': %s", progname, ch_dir, strerror (errno)); + +      if (makepidfile && pidfile) +	{ +	  FILE *fp = fopen (pidfile, "w"); +	  if (! fp) +	    eerrorx ("%s: fopen `%s': %s", progname, pidfile, strerror +		     (errno)); +	  fprintf (fp, "%d\n", mypid); +	  fclose (fp); +	} + +#ifdef HAVE_PAM +      if (changeuser != NULL) +	pamr = pam_start ("start-stop-daemon", changeuser, &conv, &pamh); +      else +	pamr = pam_start ("start-stop-daemon", "nobody", &conv, &pamh); + +      if (pamr == PAM_SUCCESS) +	pamr = pam_authenticate (pamh, PAM_SILENT); +      if (pamr == PAM_SUCCESS) +	pamr = pam_acct_mgmt (pamh, PAM_SILENT); +      if (pamr == PAM_SUCCESS) +	pamr = pam_open_session (pamh, PAM_SILENT); +      if (pamr != PAM_SUCCESS) +	eerrorx ("%s: pam error: %s", progname, pam_strerror(pamh, pamr)); +#endif + +      if ((ch_gid) && setgid(ch_gid)) +	eerrorx ("%s: unable to set groupid to %d", progname, ch_gid); +      if (changeuser && ch_gid) +	if (initgroups (changeuser, ch_gid)) +	  eerrorx ("%s: initgroups (%s, %d)", progname, changeuser, ch_gid); +      if (ch_uid && setuid (ch_uid)) +	eerrorx ("%s: unable to set userid to %d", progname, ch_uid); +      else +	{ +	  struct passwd *passwd = getpwuid (ch_uid); +	  if (passwd) +	    { +	      unsetenv ("HOME"); +	      if (passwd->pw_dir) +		setenv ("HOME", passwd->pw_dir, 1); +	      unsetenv ("USER"); +	      if (passwd->pw_name) +		setenv ("USER", passwd->pw_name, 1); +	    } +	} + +      /* Close any fd's to the passwd database */ +      endpwent (); + +#ifdef TIOCNOTTY +      ioctl(tty_fd, TIOCNOTTY, 0); +      close(tty_fd); +#endif + +      /* Clean the environment of any RC_ variables */ +      STRLIST_FOREACH (environ, env, i) +       if (env && strncmp (env, "RC_", 3) != 0) +	 { +	   /* For the path character, remove the rcscript bin dir from it */ +	   if (strncmp (env, "PATH=" RC_LIBDIR "bin:", +			strlen ("PATH=" RC_LIBDIR "bin:")) == 0) +	     { +	       char *path = env; +	       char *newpath; +	       int len; +	       path += strlen ("PATH=" RC_LIBDIR "bin:"); +	       len = sizeof (char *) * strlen (path) + 6; +	       newpath = rc_xmalloc (len); +	       snprintf (newpath, len, "PATH=%s", path); +	       newenv = rc_strlist_add (newenv, newpath); +	       free (newpath); +	     } +	   else +	     newenv = rc_strlist_add (newenv, env); +	 } + +      umask (022); + +      stdout_fd = devnull_fd; +      stderr_fd = devnull_fd; +      if (redirect_stdout) +	{ +	  if ((stdout_fd = open (redirect_stdout, O_WRONLY | O_CREAT | O_APPEND, +				 S_IRUSR | S_IWUSR)) == -1) +	    eerrorx ("%s: unable to open the logfile for stdout `%s': %s", +		     progname, redirect_stdout, strerror (errno)); +	} +      if (redirect_stderr) +	{ +	  if ((stderr_fd = open (redirect_stderr, O_WRONLY | O_CREAT | O_APPEND, +				 S_IRUSR | S_IWUSR)) == -1) +	    eerrorx ("%s: unable to open the logfile for stderr `%s': %s", +		     progname, redirect_stderr, strerror (errno)); +	} + +      dup2 (devnull_fd, STDIN_FILENO); +      if (background) +	{ +	  dup2 (stdout_fd, STDOUT_FILENO); +	  dup2 (stderr_fd, STDERR_FILENO); +	} + +      for (i = getdtablesize () - 1; i >= 3; --i) +	close(i); + +      setsid (); + +      execve (exec, argv, newenv); +#ifdef HAVE_PAM +      if (pamr == PAM_SUCCESS) +	pam_close_session (pamh, PAM_SILENT); +#endif +      eerrorx ("%s: failed to exec `%s': %s", progname, exec, strerror (errno)); +    } + +  /* Parent process */ +  if (! background) +    { +      /* As we're not backgrounding the process, wait for our pid to return */ +      int status = 0; +      int savepid = pid; + +      errno = 0; +      do +	{ +	  pid = waitpid (savepid, &status, 0); +	  if (pid < 1) +	    { +	      eerror ("waitpid %d: %s", savepid, strerror (errno)); +	      return (-1); +	    } +	} while (! WIFEXITED (status) && ! WIFSIGNALED (status)); + +      if (! WIFEXITED (status) || WEXITSTATUS (status) != 0) +	{ +	  if (! quiet) +	    eerrorx ("%s: failed to started `%s'", progname, exec); +	  exit (EXIT_FAILURE); +	} + +      pid = savepid; +    } + +  /* Wait a little bit and check that process is still running +     We do this as some badly written daemons fork and then barf */ +  if (START_WAIT > 0) +    { +      struct timeval stopat; +      struct timeval now; + +      if (gettimeofday (&stopat, NULL) != 0) +	eerrorx ("%s: gettimeofday: %s", progname, strerror (errno)); + +      stopat.tv_usec += START_WAIT; +      while (1) +	{ +	  bool alive = false; +	   +	  tv.tv_sec = 0; +	  tv.tv_usec = POLL_INTERVAL; +	  if (select (0, 0, 0, 0, &tv) < 0) +	    { +	      /* Let our signal handler handle the interupt */ +	      if (errno != EINTR) +		eerrorx ("%s: select: %s", progname, strerror (errno)); +	    } + +	  if (gettimeofday (&now, NULL) != 0) +	    eerrorx ("%s: gettimeofday: %s", progname, strerror (errno)); + +	  /* This is knarly. +	     If we backgrounded then we know the exact pid. +	     Otherwise if we have a pidfile then it *may* know the exact pid. +	     Failing that, we'll have to query processes. +	     We sleep first as some programs like ntp like to fork, and write +	     their pidfile a LONG time later. */ +	  if (background) +	    { +	      if (kill (pid, 0) == 0) +		alive = true; +	    } +	  else +	    { +	      if (pidfile && rc_exists (pidfile)) +		{ +		  if (do_stop (NULL, NULL, pidfile, uid, 0, true, false, true) > 0) +		    alive = true; +		} +	      else +		{ +		  if (do_stop (exec, cmd, NULL, uid, 0, true, false, true) > 0) +		    alive = true; +		} +	    } + +	  if (! alive) +	    eerrorx ("%s: %s died", progname, exec); + +	  if (timercmp (&now, &stopat, >)) +	    break; +	} +    } + +  if (svcname) +    rc_set_service_daemon (svcname, exec, cmd, pidfile, true); + +  exit (EXIT_SUCCESS); +} diff --git a/src/start-stop-daemon.pam b/src/start-stop-daemon.pam new file mode 100644 index 00000000..860a3d52 --- /dev/null +++ b/src/start-stop-daemon.pam @@ -0,0 +1,6 @@ +#%PAM-1.0 + +auth		sufficient	pam_rootok.so +account		required	pam_permit.so +password	required	pam_deny.so +session		optional	pam_limits.so diff --git a/src/strlist.h b/src/strlist.h new file mode 100644 index 00000000..25bbb4e0 --- /dev/null +++ b/src/strlist.h @@ -0,0 +1,24 @@ +/* +   strlist.h  +   String list macros for making char ** arrays +   Copyright 2007 Gentoo Foundation +   Based on a previous implementation by Martin Schlemmer +   Released under the GPLv2 +   */ + +#ifndef __STRLIST_H__ +#define __STRLIST_H__ + +/* FIXME: We should replace the macro with an rc_strlist_foreach +   function, but I'm unsure how to go about this. */ + +/* Step through each entry in the string list, setting '_pos' to the +   beginning of the entry.  '_counter' is used by the macro as index, +   but should not be used by code as index (or if really needed, then +   it should usually by +1 from what you expect, and should only be +   used in the scope of the macro) */ +#define STRLIST_FOREACH(_list, _pos, _counter) \ + if ((_list) && _list[0] && ((_counter = 0) == 0)) \ +   while ((_pos = _list[_counter++])) + +#endif /* __STRLIST_H__ */ | 
