diff options
author | Roy Marples <roy@marples.name> | 2008-01-05 19:25:55 +0000 |
---|---|---|
committer | Roy Marples <roy@marples.name> | 2008-01-05 19:25:55 +0000 |
commit | ac21d75300dabe83578e4373fcfd09d67c3a083b (patch) | |
tree | d5f8e2a16920add2277c79ff8a1b7e99ec2976df /src/librc | |
parent | 112fbde453d55c49b7999d2e35496a8758aaa7b5 (diff) |
Add some .mk stubs to impersonate bsk .mk files to make writing our Makefiles easier. libeinfo, librc and rc now have their own seperate directories. More work is needed to tidy this up though.
Diffstat (limited to 'src/librc')
-rw-r--r-- | src/librc/Makefile | 14 | ||||
-rw-r--r-- | src/librc/librc-daemon.c | 574 | ||||
-rw-r--r-- | src/librc/librc-depend.c | 979 | ||||
-rw-r--r-- | src/librc/librc-depend.h | 61 | ||||
-rw-r--r-- | src/librc/librc-misc.c | 245 | ||||
-rw-r--r-- | src/librc/librc-strlist.c | 230 | ||||
-rw-r--r-- | src/librc/librc.c | 891 | ||||
-rw-r--r-- | src/librc/librc.h | 127 | ||||
-rw-r--r-- | src/librc/rc.h | 446 |
9 files changed, 3567 insertions, 0 deletions
diff --git a/src/librc/Makefile b/src/librc/Makefile new file mode 100644 index 00000000..27ea942c --- /dev/null +++ b/src/librc/Makefile @@ -0,0 +1,14 @@ +TOPDIR= .. +include $(TOPDIR)/os.mk + +LIB= rc +SHLIB_MAJOR= 1 +SRCS= librc.c librc-daemon.c librc-depend.c librc-misc.c librc-strlist.c +INCS= rc.h + +CPPFLAGS+= -DLIB=\"${LIBNAME}\" + +SHLIBDIR= /${LIBNAME} + +include $(TOPDIR)/cc.mk +include $(TOPDIR)/lib.mk diff --git a/src/librc/librc-daemon.c b/src/librc/librc-daemon.c new file mode 100644 index 00000000..fe9509de --- /dev/null +++ b/src/librc/librc-daemon.c @@ -0,0 +1,574 @@ +/* + librc-daemon + Finds PID for given daemon criteria + */ + +/* + * Copyright 2007 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "librc.h" + +#if defined(__linux__) +static bool pid_is_cmd (pid_t pid, const char *cmd) +{ + char buffer[32]; + FILE *fp; + int c; + + snprintf(buffer, sizeof (buffer), "/proc/%d/stat", pid); + if ((fp = fopen (buffer, "r")) == NULL) + return (false); + + while ((c = getc (fp)) != EOF && c != '(') + ; + + if (c != '(') { + fclose(fp); + return (false); + } + + while ((c = getc (fp)) != EOF && c == *cmd) + cmd++; + + fclose (fp); + + return ((c == ')' && *cmd == '\0') ? true : false); +} + +static bool pid_is_exec (pid_t pid, const char *exec) +{ + char cmdline[32]; + char buffer[PATH_MAX]; + char *p; + int fd = -1; + int r; + + snprintf (cmdline, sizeof (cmdline), "/proc/%u/exe", pid); + memset (buffer, 0, sizeof (buffer)); + if (readlink (cmdline, buffer, sizeof (buffer)) != -1) { + if (strcmp (exec, buffer) == 0) + return (true); + + /* We should cater for deleted binaries too */ + if (strlen (buffer) > 10) { + p = buffer + (strlen (buffer) - 10); + if (strcmp (p, " (deleted)") == 0) { + *p = 0; + if (strcmp (buffer, exec) == 0) + return (true); + } + } + } + + snprintf (cmdline, sizeof (cmdline), "/proc/%u/cmdline", pid); + if ((fd = open (cmdline, O_RDONLY)) < 0) + return (false); + + r = read(fd, buffer, sizeof (buffer)); + close (fd); + + if (r == -1) + return 0; + + buffer[r] = 0; + return (strcmp (exec, buffer) == 0 ? true : false); +} + +pid_t *rc_find_pids (const char *exec, const char *cmd, + uid_t uid, pid_t pid) +{ + DIR *procdir; + struct dirent *entry; + int npids = 0; + pid_t p; + pid_t *pids = NULL; + pid_t *tmp = NULL; + char buffer[PATH_MAX]; + struct stat sb; + pid_t runscript_pid = 0; + char *pp; + + if ((procdir = opendir ("/proc")) == NULL) + return (NULL); + + /* + We never match RC_RUNSCRIPT_PID if present so we avoid the below + scenario + + /etc/init.d/ntpd stop does + start-stop-daemon --stop --name ntpd + catching /etc/init.d/ntpd stop + + nasty + */ + + if ((pp = getenv ("RC_RUNSCRIPT_PID"))) { + if (sscanf (pp, "%d", &runscript_pid) != 1) + runscript_pid = 0; + } + + while ((entry = readdir (procdir)) != NULL) { + if (sscanf (entry->d_name, "%d", &p) != 1) + continue; + + if (runscript_pid != 0 && runscript_pid == p) + continue; + + if (pid != 0 && pid != p) + continue; + + if (uid) { + snprintf (buffer, sizeof (buffer), "/proc/%d", p); + if (stat (buffer, &sb) != 0 || sb.st_uid != uid) + continue; + } + + if (cmd && ! pid_is_cmd (p, cmd)) + continue; + + if (exec && ! cmd && ! pid_is_exec (p, exec)) + continue; + + tmp = realloc (pids, sizeof (pid_t) * (npids + 2)); + if (! tmp) { + free (pids); + closedir (procdir); + errno = ENOMEM; + return (NULL); + } + pids = tmp; + + pids[npids] = p; + pids[npids + 1] = 0; + npids++; + } + closedir (procdir); + + return (pids); +} +librc_hidden_def(rc_find_pids) + +#elif BSD + +# if defined(__DragonFly__) || defined(__FreeBSD__) +# ifndef KERN_PROC_PROC +# define KERN_PROC_PROC KERN_PROC_ALL +# endif +# define _KINFO_PROC kinfo_proc +# define _KVM_GETARGV kvm_getargv +# define _GET_KINFO_UID(kp) (kp.ki_ruid) +# define _GET_KINFO_COMM(kp) (kp.ki_comm) +# define _GET_KINFO_PID(kp) (kp.ki_pid) +# else +# define _KVM_GETPROC2 +# define _KINFO_PROC kinfo_proc2 +# define _KVM_GETARGV kvm_getargv2 +# define _GET_KINFO_UID(kp) (kp.p_ruid) +# define _GET_KINFO_COMM(kp) (kp.p_comm) +# define _GET_KINFO_PID(kp) (kp.p_pid) +# endif + +pid_t *rc_find_pids (const char *exec, const char *cmd, + uid_t uid, pid_t pid) +{ + static kvm_t *kd = NULL; + char errbuf[_POSIX2_LINE_MAX]; + struct _KINFO_PROC *kp; + int i; + int processes = 0; + int argc = 0; + char **argv; + pid_t *pids = NULL; + pid_t *tmp; + int npids = 0; + + if ((kd = kvm_openfiles (NULL, NULL, NULL, O_RDONLY, errbuf)) == NULL) { + fprintf (stderr, "kvm_open: %s", errbuf); + return (NULL); + } + +#ifdef _KVM_GETPROC2 + kp = kvm_getproc2 (kd, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc2), + &processes); +#else + kp = kvm_getprocs (kd, KERN_PROC_PROC, 0, &processes); +#endif + for (i = 0; i < processes; i++) { + pid_t p = _GET_KINFO_PID (kp[i]); + if (pid != 0 && pid != p) + continue; + + if (uid != 0 && uid != _GET_KINFO_UID (kp[i])) + continue; + + if (cmd) { + if (! _GET_KINFO_COMM (kp[i]) || + strcmp (cmd, _GET_KINFO_COMM (kp[i])) != 0) + continue; + } + + if (exec && ! cmd) { + if ((argv = _KVM_GETARGV (kd, &kp[i], argc)) == NULL || ! *argv) + continue; + + if (strcmp (*argv, exec) != 0) + continue; + } + + tmp = realloc (pids, sizeof (pid_t) * (npids + 2)); + if (! tmp) { + free (pids); + kvm_close (kd); + errno = ENOMEM; + return (NULL); + } + pids = tmp; + + pids[npids] = p; + pids[npids + 1] = 0; + npids++; + } + kvm_close (kd); + + return (pids); +} +librc_hidden_def(rc_find_pids) + +#else +# error "Platform not supported!" +#endif + +static bool _match_daemon (const char *path, const char *file, + const char *mexec, const char *mname, + const char *mpidfile) +{ + char *buffer; + char *ffile = rc_strcatpaths (path, file, (char *) NULL); + FILE *fp; + int lc = 0; + int m = 0; + + if ((fp = fopen (ffile, "r")) == NULL) { + free (ffile); + return (false); + } + + if (! mname) + m += 10; + if (! mpidfile) + m += 100; + + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + memset (buffer, 0, RC_LINEBUFFER); + while ((fgets (buffer, RC_LINEBUFFER, fp))) { + int lb = strlen (buffer) - 1; + if (buffer[lb] == '\n') + buffer[lb] = 0; + + if (strcmp (buffer, mexec) == 0) + m += 1; + else if (mname && strcmp (buffer, mname) == 0) + m += 10; + else if (mpidfile && strcmp (buffer, mpidfile) == 0) + m += 100; + + if (m == 111) + break; + + lc++; + if (lc > 5) + break; + } + free (buffer); + fclose (fp); + free (ffile); + + return (m == 111 ? true : false); +} + +bool rc_service_daemon_set (const char *service, const char *exec, + const char *name, const char *pidfile, + bool started) +{ + char *dirpath; + char *file = NULL; + int i; + char *mexec; + char *mname; + char *mpidfile; + int nfiles = 0; + char *oldfile = NULL; + bool retval = false; + DIR *dp; + struct dirent *d; + + if (! exec && ! name && ! pidfile) { + errno = EINVAL; + return (false); + } + + dirpath = rc_strcatpaths (RC_SVCDIR, "daemons", + basename_c (service), (char *) NULL); + + if (exec) { + i = strlen (exec) + 6; + mexec = xmalloc (sizeof (char) * i); + snprintf (mexec, i, "exec=%s", exec); + } else + mexec = xstrdup ("exec="); + + if (name) { + i = strlen (name) + 6; + mname = xmalloc (sizeof (char) * i); + snprintf (mname, i, "name=%s", name); + } else + mname = xstrdup ("name="); + + if (pidfile) { + i = strlen (pidfile) + 9; + mpidfile = xmalloc (sizeof (char) * i); + snprintf (mpidfile, i, "pidfile=%s", pidfile); + } else + mpidfile = xstrdup ("pidfile="); + + /* Regardless, erase any existing daemon info */ + if ((dp = opendir (dirpath))) { + while ((d = readdir (dp))) { + if (d->d_name[0] == '.') + continue; + file = rc_strcatpaths (dirpath, d->d_name, (char *) NULL); + nfiles++; + + if (! oldfile) { + if (_match_daemon (dirpath, d->d_name, + mexec, mname, mpidfile)) + { + unlink (file); + oldfile = file; + nfiles--; + } + } else { + rename (file, oldfile); + free (oldfile); + oldfile = file; + } + } + free (file); + closedir (dp); + } + + /* Now store our daemon info */ + if (started) { + char buffer[10]; + FILE *fp; + + if (mkdir (dirpath, 0755) == 0 || errno == EEXIST) { + snprintf (buffer, sizeof (buffer), "%03d", nfiles + 1); + file = rc_strcatpaths (dirpath, buffer, (char *) NULL); + if ((fp = fopen (file, "w"))) { + fprintf (fp, "%s\n%s\n%s\n", mexec, mname, mpidfile); + fclose (fp); + retval = true; + } + free (file); + } + } else + retval = true; + + free (mexec); + free (mname); + free (mpidfile); + free (dirpath); + + return (retval); +} +librc_hidden_def(rc_service_daemon_set) + +bool rc_service_started_daemon (const char *service, const char *exec, + int indx) +{ + char *dirpath; + char *file; + int i; + char *mexec; + bool retval = false; + DIR *dp; + struct dirent *d; + + if (! service || ! exec) + return (false); + + dirpath = rc_strcatpaths (RC_SVCDIR, "daemons", basename_c (service), + (char *) NULL); + + i = strlen (exec) + 6; + mexec = xmalloc (sizeof (char) * i); + snprintf (mexec, i, "exec=%s", exec); + + if (indx > 0) { + int len = sizeof (char) * 10; + file = xmalloc (len); + snprintf (file, len, "%03d", indx); + retval = _match_daemon (dirpath, file, mexec, NULL, NULL); + free (file); + } else { + if ((dp = opendir (dirpath))) { + while ((d = readdir (dp))) { + if (d->d_name[0] == ',') + continue; + retval = _match_daemon (dirpath, d->d_name, mexec, NULL, NULL); + if (retval) + break; + } + closedir (dp); + } + } + + free (dirpath); + free (mexec); + return (retval); +} +librc_hidden_def(rc_service_started_daemon) + +bool rc_service_daemons_crashed (const char *service) +{ + char *dirpath; + DIR *dp; + struct dirent *d; + char *path; + FILE *fp; + char *buffer = NULL; + char *exec = NULL; + char *name = NULL; + char *pidfile = NULL; + pid_t pid = 0; + pid_t *pids = NULL; + char *p; + char *token; + bool retval = false; + + if (! service) + return (false); + + dirpath = rc_strcatpaths (RC_SVCDIR, "daemons", basename_c (service), + (char *) NULL); + + if (! (dp = opendir (dirpath))) { + free (dirpath); + return (false); + } + + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + memset (buffer, 0, RC_LINEBUFFER); + + while ((d = readdir (dp))) { + if (d->d_name[0] == '.') + continue; + + path = rc_strcatpaths (dirpath, d->d_name, (char *) NULL); + fp = fopen (path, "r"); + free (path); + if (! fp) + break; + + while ((fgets (buffer, RC_LINEBUFFER, fp))) { + int lb = strlen (buffer) - 1; + if (buffer[lb] == '\n') + buffer[lb] = 0; + + p = buffer; + if ((token = strsep (&p, "=")) == NULL || ! p) + continue; + + if (strlen (p) == 0) + continue; + + if (strcmp (token, "exec") == 0) { + if (exec) + free (exec); + exec = xstrdup (p); + } else if (strcmp (token, "name") == 0) { + if (name) + free (name); + name = xstrdup (p); + } else if (strcmp (token, "pidfile") == 0) { + if (pidfile) + free (pidfile); + pidfile = xstrdup (p); + } + } + fclose (fp); + + pid = 0; + if (pidfile) { + if (! exists (pidfile)) { + retval = true; + break; + } + + if ((fp = fopen (pidfile, "r")) == NULL) { + retval = true; + break; + } + + if (fscanf (fp, "%d", &pid) != 1) { + fclose (fp); + retval = true; + break; + } + + fclose (fp); + free (pidfile); + pidfile = NULL; + + /* We have the pid, so no need to match on name */ + free (exec); + exec = NULL; + free (name); + name = NULL; + } + + if ((pids = rc_find_pids (exec, name, 0, pid)) == NULL) { + retval = true; + break; + } + free (pids); + + free (exec); + exec = NULL; + free (name); + name = NULL; + } + + free (buffer); + free (exec); + free (name); + free (dirpath); + closedir (dp); + + return (retval); +} +librc_hidden_def(rc_service_daemons_crashed) diff --git a/src/librc/librc-depend.c b/src/librc/librc-depend.c new file mode 100644 index 00000000..902d012f --- /dev/null +++ b/src/librc/librc-depend.c @@ -0,0 +1,979 @@ +/* + librc-depend + rc service dependency and ordering + */ + +/* + * Copyright 2007 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "librc.h" + +#define GENDEP RC_LIBDIR "/sh/gendepends.sh" + +#define RC_DEPCONFIG RC_SVCDIR "/depconfig" + +static const char *bootlevel = NULL; + +/* We use this so we can pass our char array through many functions */ +struct lhead +{ + char **list; +}; + +static char *get_shell_value (char *string) +{ + char *p = string; + char *e; + + if (! string) + return (NULL); + + if (*p == '\'') + p++; + + e = p + strlen (p) - 1; + if (*e == '\n') + *e-- = 0; + if (*e == '\'') + *e-- = 0; + + if (*p != 0) + return p; + + return (NULL); +} + +void rc_deptree_free (rc_depinfo_t *deptree) +{ + rc_depinfo_t *di = deptree; + while (di) + { + rc_depinfo_t *dip = di->next; + rc_deptype_t *dt = di->depends; + free (di->service); + while (dt) + { + rc_deptype_t *dtp = dt->next; + free (dt->type); + rc_strlist_free (dt->services); + free (dt); + dt = dtp; + } + free (di); + di = dip; + } +} +librc_hidden_def(rc_deptree_free) + +static rc_depinfo_t *get_depinfo (const rc_depinfo_t *deptree, + const char *service) +{ + const rc_depinfo_t *di; + + if (! deptree || ! service) + return (NULL); + + for (di = deptree; di; di = di->next) + if (strcmp (di->service, service) == 0) + return ((rc_depinfo_t *)di); + + return (NULL); +} + +static rc_deptype_t *get_deptype (const rc_depinfo_t *depinfo, + const char *type) +{ + rc_deptype_t *dt; + + if (! depinfo || !type) + return (NULL); + + for (dt = depinfo->depends; dt; dt = dt->next) + if (strcmp (dt->type, type) == 0) + return (dt); + + return (NULL); +} + +rc_depinfo_t *rc_deptree_load (void) +{ + FILE *fp; + rc_depinfo_t *deptree = NULL; + rc_depinfo_t *depinfo = NULL; + rc_deptype_t *deptype = NULL; + char buffer [RC_LINEBUFFER]; + char *type; + char *p; + char *e; + int i; + + if (! (fp = fopen (RC_DEPTREE, "r"))) + return (NULL); + + while (fgets (buffer, RC_LINEBUFFER, fp)) + { + p = buffer; + e = strsep (&p, "_"); + if (! e || strcmp (e, "depinfo") != 0) + continue; + + e = strsep (&p, "_"); + if (! e || sscanf (e, "%d", &i) != 1) + continue; + + if (! (type = strsep (&p, "_="))) + continue; + + if (strcmp (type, "service") == 0) + { + /* Sanity */ + e = get_shell_value (p); + if (! e || strlen (e) == 0) + continue; + + if (! deptree) + { + deptree = xmalloc (sizeof (rc_depinfo_t)); + depinfo = deptree; + } + else + { + depinfo->next = xmalloc (sizeof (rc_depinfo_t)); + depinfo = depinfo->next; + } + memset (depinfo, 0, sizeof (rc_depinfo_t)); + depinfo->service = xstrdup (e); + deptype = NULL; + continue; + } + + e = strsep (&p, "="); + if (! e || sscanf (e, "%d", &i) != 1) + continue; + + /* Sanity */ + e = get_shell_value (p); + if (! e || strlen (e) == 0) + continue; + + if (! deptype) + { + depinfo->depends = xmalloc (sizeof (rc_deptype_t)); + deptype = depinfo->depends; + memset (deptype, 0, sizeof (rc_deptype_t)); + } + else + if (strcmp (deptype->type, type) != 0) + { + deptype->next = xmalloc (sizeof (rc_deptype_t)); + deptype = deptype->next; + memset (deptype, 0, sizeof (rc_deptype_t)); + } + + if (! deptype->type) + deptype->type = xstrdup (type); + + rc_strlist_addsort (&deptype->services, e); + } + fclose (fp); + + return (deptree); +} +librc_hidden_def(rc_deptree_load) + +static bool valid_service (const char *runlevel, const char *service) +{ + rc_service_state_t state = rc_service_state (service); + + return ((strcmp (runlevel, bootlevel) != 0 && + rc_service_in_runlevel (service, bootlevel)) || + rc_service_in_runlevel (service, runlevel) || + state & RC_SERVICE_COLDPLUGGED || + state & RC_SERVICE_STARTED); +} + +static bool get_provided1 (const char *runlevel, struct lhead *providers, + rc_deptype_t *deptype, + const char *level, bool coldplugged, + rc_service_state_t state) +{ + char *service; + int i; + bool retval = false; + + STRLIST_FOREACH (deptype->services, service, i) + { + bool ok = true; + rc_service_state_t s = rc_service_state (service); + if (level) + ok = rc_service_in_runlevel (service, level); + else if (coldplugged) + ok = (s & RC_SERVICE_COLDPLUGGED && + ! rc_service_in_runlevel (service, runlevel) && + ! rc_service_in_runlevel (service, bootlevel)); + + if (! ok) + continue; + + switch (state) { + case RC_SERVICE_STARTED: + ok = (s & RC_SERVICE_STARTED); + break; + case RC_SERVICE_INACTIVE: + case RC_SERVICE_STARTING: + case RC_SERVICE_STOPPING: + ok = (s & RC_SERVICE_STARTING || + s & RC_SERVICE_STOPPING || + s & RC_SERVICE_INACTIVE); + break; + default: + break; + } + + if (! ok) + continue; + + retval = true; + rc_strlist_add (&providers->list, service); + } + + return (retval); +} + +/* Work out if a service is provided by another service. + For example metalog provides logger. + We need to be able to handle syslogd providing logger too. + We do this by checking whats running, then what's starting/stopping, + then what's run in the runlevels and finally alphabetical order. + + If there are any bugs in rc-depend, they will probably be here as + provided dependancy can change depending on runlevel state. + */ +static char **get_provided (const rc_depinfo_t *deptree, + const rc_depinfo_t *depinfo, + const char *runlevel, int options) +{ + rc_deptype_t *dt; + struct lhead providers; + char *service; + int i; + + if (! deptree || ! depinfo) + return (NULL); + if (rc_service_exists (depinfo->service)) + return (NULL); + + dt = get_deptype (depinfo, "providedby"); + if (! dt) + return (NULL); + + memset (&providers, 0, sizeof (struct lhead)); + /* If we are stopping then all depends are true, regardless of state. + This is especially true for net services as they could force a restart + of the local dns resolver which may depend on net. */ + if (options & RC_DEP_STOP) + { + STRLIST_FOREACH (dt->services, service, i) + rc_strlist_add (&providers.list, service); + + return (providers.list); + } + + /* If we're strict, then only use what we have in our runlevel + * and bootlevel */ + if (options & RC_DEP_STRICT) + { + STRLIST_FOREACH (dt->services, service, i) + if (rc_service_in_runlevel (service, runlevel) || + rc_service_in_runlevel (service, bootlevel)) + rc_strlist_add (&providers.list, service); + + if (providers.list) + return (providers.list); + } + + /* OK, we're not strict or there were no services in our runlevel. + This is now where the logic gets a little fuzzy :) + If there is >1 running service then we return NULL. + We do this so we don't hang around waiting for inactive services and + our need has already been satisfied as it's not strict. + We apply this to our runlevel, coldplugged services, then bootlevel + and finally any running.*/ +#define DO \ + if (providers.list && providers.list[0] && providers.list[1]) \ + { \ + rc_strlist_free (providers.list); \ + return (NULL); \ + } \ + else if (providers.list) \ + return providers.list; \ + + /* Anything in the runlevel has to come first */ + if (get_provided1 (runlevel, &providers, dt, runlevel, false, RC_SERVICE_STARTED)) + { DO } + if (get_provided1 (runlevel, &providers, dt, runlevel, false, RC_SERVICE_STARTING)) + return (providers.list); + if (get_provided1 (runlevel, &providers, dt, runlevel, false, RC_SERVICE_STOPPED)) + return (providers.list); + + /* Check coldplugged services */ + if (get_provided1 (runlevel, &providers, dt, NULL, true, RC_SERVICE_STARTED)) + { DO } + if (get_provided1 (runlevel, &providers, dt, NULL, true, RC_SERVICE_STARTING)) + return (providers.list); + + /* Check bootlevel if we're not in it */ + if (bootlevel && strcmp (runlevel, bootlevel) != 0) + { + if (get_provided1 (runlevel, &providers, dt, bootlevel, false, RC_SERVICE_STARTED)) + { DO } + if (get_provided1 (runlevel, &providers, dt, bootlevel, false, RC_SERVICE_STARTING)) + return (providers.list); + } + + /* Check coldplugged services */ + if (get_provided1 (runlevel, &providers, dt, NULL, true, RC_SERVICE_STOPPED)) + { DO } + + /* Check manually started */ + if (get_provided1 (runlevel, &providers, dt, NULL, false, RC_SERVICE_STARTED)) + { DO } + if (get_provided1 (runlevel, &providers, dt, NULL, false, RC_SERVICE_STARTING)) + return (providers.list); + + /* Nothing started then. OK, lets get the stopped services */ + if (get_provided1 (runlevel, &providers, dt, runlevel, false, RC_SERVICE_STOPPED)) + return (providers.list); + + if (bootlevel && (strcmp (runlevel, bootlevel) != 0) + && (get_provided1 (runlevel, &providers, dt, bootlevel, false, RC_SERVICE_STOPPED))) + return (providers.list); + + /* Still nothing? OK, list all services */ + STRLIST_FOREACH (dt->services, service, i) + rc_strlist_add (&providers.list, service); + + return (providers.list); +} + +static void visit_service (const rc_depinfo_t *deptree, + const char * const *types, + struct lhead *sorted, struct lhead *visited, + const rc_depinfo_t *depinfo, + const char *runlevel, int options) +{ + int i, j, k; + char *lp; + const char *item; + char *service; + rc_depinfo_t *di; + rc_deptype_t *dt; + char **provides; + char *svcname; + + if (! deptree || !sorted || !visited || !depinfo) + return; + + /* Check if we have already visited this service or not */ + STRLIST_FOREACH (visited->list, item, i) + if (strcmp (item, depinfo->service) == 0) + return; + + /* Add ourselves as a visited service */ + rc_strlist_add (&visited->list, depinfo->service); + + STRLIST_FOREACH (types, item, i) + { + if ((dt = get_deptype (depinfo, item))) + { + STRLIST_FOREACH (dt->services, service, j) + { + if (! options & RC_DEP_TRACE || strcmp (item, "iprovide") == 0) + { + rc_strlist_add (&sorted->list, service); + continue; + } + + di = get_depinfo (deptree, service); + if ((provides = get_provided (deptree, di, runlevel, options))) + { + STRLIST_FOREACH (provides, lp, k) + { + di = get_depinfo (deptree, lp); + if (di && (strcmp (item, "ineed") == 0 || + strcmp (item, "needsme") == 0 || + valid_service (runlevel, di->service))) + visit_service (deptree, types, sorted, visited, di, + runlevel, options | RC_DEP_TRACE); + } + rc_strlist_free (provides); + } + else + if (di && (strcmp (item, "ineed") == 0 || + strcmp (item, "needsme") == 0 || + valid_service (runlevel, service))) + visit_service (deptree, types, sorted, visited, di, + runlevel, options | RC_DEP_TRACE); + } + } + } + + /* Now visit the stuff we provide for */ + if (options & RC_DEP_TRACE && + (dt = get_deptype (depinfo, "iprovide"))) + { + STRLIST_FOREACH (dt->services, service, i) + { + if ((di = get_depinfo (deptree, service))) + if ((provides = get_provided (deptree, di, runlevel, options))) + { + STRLIST_FOREACH (provides, lp, j) + if (strcmp (lp, depinfo->service) == 0) + { + visit_service (deptree, types, sorted, visited, di, + runlevel, options | RC_DEP_TRACE); + break; + } + rc_strlist_free (provides); + } + } + } + + /* We've visited everything we need, so add ourselves unless we + are also the service calling us or we are provided by something */ + svcname = getenv("SVCNAME"); + if (! svcname || strcmp (svcname, depinfo->service) != 0) + if (! get_deptype (depinfo, "providedby")) + rc_strlist_add (&sorted->list, depinfo->service); +} + +char **rc_deptree_depend (const rc_depinfo_t *deptree, + const char *service, const char *type) +{ + rc_depinfo_t *di; + rc_deptype_t *dt; + char **svcs = NULL; + int i; + char *svc; + + if (! (di = get_depinfo (deptree, service)) || + ! (dt = get_deptype (di, type))) + { + errno = ENOENT; + return (NULL); + } + + /* For consistency, we copy the array */ + STRLIST_FOREACH (dt->services, svc, i) + rc_strlist_add (&svcs, svc); + + return (svcs); +} +librc_hidden_def(rc_deptree_depend) + +char **rc_deptree_depends (const rc_depinfo_t *deptree, + const char *const *types, + const char *const *services, + const char *runlevel, int options) +{ + struct lhead sorted; + struct lhead visited; + rc_depinfo_t *di; + const char *service; + int i; + + if (! deptree || ! services) + return (NULL); + + memset (&sorted, 0, sizeof (struct lhead)); + memset (&visited, 0, sizeof (struct lhead)); + + bootlevel = getenv ("RC_BOOTLEVEL"); + if (! bootlevel) + bootlevel = RC_LEVEL_BOOT; + + STRLIST_FOREACH (services, service, i) + { + if (! (di = get_depinfo (deptree, service))) { + errno = ENOENT; + continue; + } + if (types) + visit_service (deptree, types, &sorted, &visited, + di, runlevel, options); + } + + rc_strlist_free (visited.list); + return (sorted.list); +} +librc_hidden_def(rc_deptree_depends) + +static const char * const order_types[] = { "ineed", "iuse", "iafter", NULL }; +char **rc_deptree_order (const rc_depinfo_t *deptree, const char *runlevel, + int options) +{ + char **list = NULL; + char **services = NULL; + bool reverse = false; + char **tmp = NULL; + + if (! runlevel) + return (NULL); + + bootlevel = getenv ("RC_BOOTLEVEL"); + if (! bootlevel) + bootlevel = RC_LEVEL_BOOT; + + /* When shutting down, list all running services */ + if (strcmp (runlevel, RC_LEVEL_SINGLE) == 0 || + strcmp (runlevel, RC_LEVEL_SHUTDOWN) == 0 || + strcmp (runlevel, RC_LEVEL_REBOOT) == 0) + { + list = rc_services_in_state (RC_SERVICE_STARTED); + + tmp = rc_services_in_state (RC_SERVICE_INACTIVE); + rc_strlist_join (&list, tmp); + rc_strlist_free (tmp); + + tmp = rc_services_in_state (RC_SERVICE_STARTING); + rc_strlist_join (&list, tmp); + rc_strlist_free (tmp); + reverse = true; + } else { + list = rc_services_in_runlevel (runlevel); + + /* Add coldplugged services */ + tmp = rc_services_in_state (RC_SERVICE_COLDPLUGGED); + rc_strlist_join (&list, tmp); + rc_strlist_free (tmp); + + /* If we're not the boot runlevel then add that too */ + if (strcmp (runlevel, bootlevel) != 0) { + tmp = rc_services_in_runlevel (bootlevel); + rc_strlist_join (&list, tmp); + rc_strlist_free (tmp); + } + } + + /* Now we have our lists, we need to pull in any dependencies + and order them */ + services = rc_deptree_depends (deptree, order_types, (const char **) list, + runlevel, + RC_DEP_STRICT | RC_DEP_TRACE | options); + rc_strlist_free (list); + + if (reverse) + rc_strlist_reverse (services); + + return (services); +} +librc_hidden_def(rc_deptree_order) + +static bool is_newer_than (const char *file, const char *target) +{ + struct stat buf; + time_t mtime; + bool newer = true; + DIR *dp; + struct dirent *d; + char *path; + + if (stat (file, &buf) != 0 || buf.st_size == 0) + return (false); + mtime = buf.st_mtime; + + /* Of course we are newever than targets that don't exist + Such as broken symlinks */ + if (stat (target, &buf) != 0) + return (true); + + if (mtime < buf.st_mtime) + return (false); + + if (! (dp = opendir (target))) + return (true); + + while ((d = readdir (dp))) { + if (d->d_name[0] == '.') + continue; + + path = rc_strcatpaths (target, d->d_name, (char *) NULL); + newer = is_newer_than (file, path); + free (path); + if (! newer) + break; + } + closedir (dp); + + return (newer); +} + +typedef struct deppair +{ + const char *depend; + const char *addto; +} deppair_t; + +static const deppair_t deppairs[] = { + { "ineed", "needsme" }, + { "iuse", "usesme" }, + { "iafter", "ibefore" }, + { "ibefore", "iafter" }, + { "iprovide", "providedby" }, + { NULL, NULL } +}; + +static const char *depdirs[] = +{ + RC_SVCDIR "/starting", + RC_SVCDIR "/started", + RC_SVCDIR "/stopping", + RC_SVCDIR "/inactive", + RC_SVCDIR "/wasinactive", + RC_SVCDIR "/failed", + RC_SVCDIR "/coldplugged", + RC_SVCDIR "/daemons", + RC_SVCDIR "/options", + RC_SVCDIR "/exclusive", + RC_SVCDIR "/scheduled", + NULL +}; + +bool rc_deptree_update_needed (void) +{ + bool newer = false; + char **config; + char *service; + int i; + + /* Create base directories if needed */ + for (i = 0; depdirs[i]; i++) + if (mkdir (depdirs[i], 0755) != 0 && errno != EEXIST) + fprintf (stderr, "mkdir `%s': %s\n", depdirs[i], strerror (errno)); + + /* Quick test to see if anything we use has changed */ + if (! is_newer_than (RC_DEPTREE, RC_INITDIR) || + ! is_newer_than (RC_DEPTREE, RC_CONFDIR) || + ! is_newer_than (RC_DEPTREE, RC_INITDIR_LOCAL) || + ! is_newer_than (RC_DEPTREE, RC_CONFDIR_LOCAL) || + ! is_newer_than (RC_DEPTREE, "/etc/rc.conf")) + return (true); + + /* Some init scripts dependencies change depending on config files + * outside of baselayout, like syslog-ng, so we check those too. */ + config = rc_config_list (RC_DEPCONFIG); + STRLIST_FOREACH (config, service, i) { + if (! is_newer_than (RC_DEPTREE, service)) { + newer = true; + break; + } + } + rc_strlist_free (config); + + return (newer); +} +librc_hidden_def(rc_deptree_update_needed) + +/* This is a 5 phase operation + Phase 1 is a shell script which loads each init script and config in turn + and echos their dependency info to stdout + Phase 2 takes that and populates a depinfo object with that data + Phase 3 adds any provided services to the depinfo object + Phase 4 scans that depinfo object and puts in backlinks + Phase 5 saves the depinfo object to disk + */ +bool rc_deptree_update (void) +{ + char *depends; + char *service; + char *type; + char *depend; + char **config = NULL; + int retval = true; + FILE *fp; + rc_depinfo_t *deptree; + rc_depinfo_t *depinfo; + rc_depinfo_t *di; + rc_depinfo_t *last_depinfo = NULL; + rc_deptype_t *deptype = NULL; + rc_deptype_t *dt; + rc_deptype_t *last_deptype = NULL; + char *buffer = NULL; + int len; + int i; + int j; + int k; + bool already_added; + + /* Some init scripts need RC_LIBDIR to source stuff + Ideally we should be setting our full env instead */ + if (! getenv ("RC_LIBDIR")) + setenv ("RC_LIBDIR", RC_LIBDIR, 0); + + /* Phase 1 */ + if (! (fp = popen (GENDEP, "r"))) + return (false); + + deptree = xmalloc (sizeof (rc_depinfo_t)); + memset (deptree, 0, sizeof (rc_depinfo_t)); + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + memset (buffer, 0, RC_LINEBUFFER); + + /* Phase 2 */ + while (fgets (buffer, RC_LINEBUFFER, fp)) + { + /* Trim the newline */ + if (buffer[strlen (buffer) - 1] == '\n') + buffer[strlen(buffer) -1] = 0; + + depends = buffer; + service = strsep (&depends, " "); + if (! service) + continue; + type = strsep (&depends, " "); + + for (depinfo = deptree; depinfo; depinfo = depinfo->next) + { + last_depinfo = depinfo; + if (depinfo->service && strcmp (depinfo->service, service) == 0) + break; + } + + if (! depinfo) + { + if (! last_depinfo->service) + depinfo = last_depinfo; + else + { + last_depinfo->next = xmalloc (sizeof (rc_depinfo_t)); + depinfo = last_depinfo->next; + } + memset (depinfo, 0, sizeof (rc_depinfo_t)); + depinfo->service = xstrdup (service); + } + + /* We may not have any depends */ + if (! type || ! depends) + continue; + + /* Get the type */ + if (strcmp (type, "config") != 0) { + last_deptype = NULL; + for (deptype = depinfo->depends; deptype; deptype = deptype->next) + { + last_deptype = deptype; + if (strcmp (deptype->type, type) == 0) + break; + } + + if (! deptype) + { + if (! last_deptype) + { + depinfo->depends = xmalloc (sizeof (rc_deptype_t)); + deptype = depinfo->depends; + } + else + { + last_deptype->next = xmalloc (sizeof (rc_deptype_t)); + deptype = last_deptype->next; + } + memset (deptype, 0, sizeof (rc_deptype_t)); + deptype->type = xstrdup (type); + } + } + + /* Now add each depend to our type. + We do this individually so we handle multiple spaces gracefully */ + while ((depend = strsep (&depends, " "))) + { + if (depend[0] == 0) + continue; + + if (strcmp (type, "config") == 0) { + rc_strlist_addsort (&config, depend); + continue; + } + + /* .sh files are not init scripts */ + len = strlen (depend); + if (len > 2 && + depend[len - 3] == '.' && + depend[len - 2] == 's' && + depend[len - 1] == 'h') + continue; + + rc_strlist_addsort (&deptype->services, depend); + + /* We need to allow `after *; before local;` to work. + * Conversely, we need to allow 'before *; after modules' also */ + /* If we're before something, remove us from the after list */ + if (strcmp (type, "ibefore") == 0) { + if ((dt = get_deptype (depinfo, "iafter"))) + rc_strlist_delete (&dt->services, depend); + } + /* If we're after something, remove us from the before list */ + if (strcmp (type, "iafter") == 0 || + strcmp (type, "ineed") == 0 || + strcmp (type, "iuse") == 0) { + if ((dt = get_deptype (depinfo, "ibefore"))) + rc_strlist_delete (&dt->services, depend); + } + } + } + pclose (fp); + free (buffer); + + /* Phase 3 - add our providors to the tree */ + for (depinfo = deptree; depinfo; depinfo = depinfo->next) + { + if ((deptype = get_deptype (depinfo, "iprovide"))) + STRLIST_FOREACH (deptype->services, service, i) + { + for (di = deptree; di; di = di->next) + { + last_depinfo = di; + if (strcmp (di->service, service) == 0) + break; + } + if (! di) + { + last_depinfo->next = xmalloc (sizeof (rc_depinfo_t)); + di = last_depinfo->next; + memset (di, 0, sizeof (rc_depinfo_t)); + di->service = xstrdup (service); + } + } + } + + /* Phase 4 - backreference our depends */ + for (depinfo = deptree; depinfo; depinfo = depinfo->next) + { + for (i = 0; deppairs[i].depend; i++) + { + deptype = get_deptype (depinfo, deppairs[i].depend); + if (! deptype) + continue; + + STRLIST_FOREACH (deptype->services, service, j) + { + di = get_depinfo (deptree, service); + if (! di) + { + if (strcmp (deptype->type, "ineed") == 0) + { + fprintf (stderr, + "Service `%s' needs non existant service `%s'\n", + depinfo->service, service); + } + continue; + } + + /* Add our deptype now */ + last_deptype = NULL; + for (dt = di->depends; dt; dt = dt->next) + { + last_deptype = dt; + if (strcmp (dt->type, deppairs[i].addto) == 0) + break; + } + if (! dt) + { + if (! last_deptype) + { + di->depends = xmalloc (sizeof (rc_deptype_t)); + dt = di->depends; + } + else + { + last_deptype->next = xmalloc (sizeof (rc_deptype_t)); + dt = last_deptype->next; + } + memset (dt, 0, sizeof (rc_deptype_t)); + dt->type = xstrdup (deppairs[i].addto); + } + + already_added = false; + STRLIST_FOREACH (dt->services, service, k) + if (strcmp (service, depinfo->service) == 0) + { + already_added = true; + break; + } + + if (! already_added) + rc_strlist_addsort (&dt->services, depinfo->service); + } + } + } + + /* Phase 5 - save to disk + Now that we're purely in C, do we need to keep a shell parseable file? + I think yes as then it stays human readable + This works and should be entirely shell parseable provided that depend + names don't have any non shell variable characters in + */ + if ((fp = fopen (RC_DEPTREE, "w"))) { + i = 0; + for (depinfo = deptree; depinfo; depinfo = depinfo->next) + { + fprintf (fp, "depinfo_%d_service='%s'\n", i, depinfo->service); + for (deptype = depinfo->depends; deptype; deptype = deptype->next) + { + k = 0; + STRLIST_FOREACH (deptype->services, service, j) + { + fprintf (fp, "depinfo_%d_%s_%d='%s'\n", i, deptype->type, + k, service); + k++; + } + } + i++; + } + fclose (fp); + } else { + fprintf (stderr, "fopen `%s': %s\n", RC_DEPTREE, strerror (errno)); + retval = false; + } + + /* Save our external config files to disk */ + if (config) { + if ((fp = fopen (RC_DEPCONFIG, "w"))) { + STRLIST_FOREACH (config, service, i) + fprintf (fp, "%s\n", service); + fclose (fp); + } else { + fprintf (stderr, "fopen `%s': %s\n", RC_DEPCONFIG, strerror (errno)); + retval = false; + } + rc_strlist_free (config); + } + + rc_deptree_free (deptree); + + return (retval); +} +librc_hidden_def(rc_deptree_update) diff --git a/src/librc/librc-depend.h b/src/librc/librc-depend.h new file mode 100644 index 00000000..238f70d1 --- /dev/null +++ b/src/librc/librc-depend.h @@ -0,0 +1,61 @@ +/* + * librc-depend.h + * Internal header file for dependency structures + */ + +/* + * Copyright 2007 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _LIBRC_DEPEND_H +#define _LIBRC_DEPEND_H + +/*! @name Dependency structures + * private to librc - rc.h exposes them just a pointers */ + +/*! Singly linked list of dependency types that list the services the + * type is for */ +typedef struct rc_deptype +{ + /*! ineed, iuse, iafter, etc */ + char *type; + /*! NULL terminated list of services */ + char **services; + /*! Next dependency type */ + struct rc_deptype *next; +} rc_deptype_t; + +/*! Singly linked list of services and their dependencies */ +typedef struct rc_depinfo +{ + /*! Name of service */ + char *service; + /*! Dependencies */ + rc_deptype_t *depends; + /*! Next service dependency type */ + struct rc_depinfo *next; +} rc_depinfo_t; + +#endif diff --git a/src/librc/librc-misc.c b/src/librc/librc-misc.c new file mode 100644 index 00000000..dcecc293 --- /dev/null +++ b/src/librc/librc-misc.c @@ -0,0 +1,245 @@ +/* + rc-misc.c + rc misc functions + */ + +/* + * Copyright 2007 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "librc.h" + +bool rc_yesno (const char *value) +{ + if (! value) { + errno = ENOENT; + return (false); + } + + if (strcasecmp (value, "yes") == 0 || + strcasecmp (value, "y") == 0 || + strcasecmp (value, "true") == 0 || + strcasecmp (value, "1") == 0) + return (true); + + if (strcasecmp (value, "no") != 0 && + strcasecmp (value, "n") != 0 && + strcasecmp (value, "false") != 0 && + strcasecmp (value, "0") != 0) + errno = EINVAL; + + return (false); +} +librc_hidden_def(rc_yesno) + +char *rc_strcatpaths (const char *path1, const char *paths, ...) +{ + va_list ap; + int length; + int i; + char *p; + char *path; + char *pathp; + + if (! path1 || ! paths) + return (NULL); + + length = strlen (path1) + strlen (paths) + 1; + if (*paths != '/') + length ++; + + va_start (ap, paths); + while ((p = va_arg (ap, char *)) != NULL) { + if (*p != '/') + length ++; + length += strlen (p); + } + va_end (ap); + + pathp = path = xmalloc (length * sizeof (char)); + memset (path, 0, length); + i = strlen (path1); + memcpy (path, path1, i); + pathp += i; + if (*paths != '/') + *pathp ++ = '/'; + i = strlen (paths); + memcpy (pathp, paths, i); + pathp += i; + + va_start (ap, paths); + while ((p = va_arg (ap, char *)) != NULL) { + if (*p != '/') + *pathp ++= '/'; + i = strlen (p); + memcpy (pathp, p, i); + pathp += i; + } + va_end (ap); + + *pathp++ = 0; + + return (path); +} +librc_hidden_def(rc_strcatpaths) + + +char **rc_config_load (const char *file) +{ + char **list = NULL; + FILE *fp; + char *buffer; + char *p; + char *token; + char *line; + char *linep; + char *linetok; + int i = 0; + bool replaced; + char *entry; + char *newline; + + if (! (fp = fopen (file, "r"))) + return (NULL); + + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + while (fgets (buffer, RC_LINEBUFFER, fp)) { + p = buffer; + + /* Strip leading spaces/tabs */ + while ((*p == ' ') || (*p == '\t')) + p++; + + if (! p || strlen (p) < 3 || p[0] == '#') + continue; + + /* Get entry */ + token = strsep (&p, "="); + + if (! token) + continue; + + entry = xstrdup (token); + + /* Preserve shell coloring */ + if (*p == '$') + token = p; + else + do { + /* Bash variables are usually quoted */ + token = strsep (&p, "\"\'"); + } while ((token) && (strlen (token) == 0)); + + /* Drop a newline if that's all we have */ + i = strlen (token) - 1; + if (token[i] == 10) + token[i] = 0; + + i = strlen (entry) + strlen (token) + 2; + newline = xmalloc (i); + snprintf (newline, i, "%s=%s", entry, token); + + replaced = false; + /* In shells the last item takes precedence, so we need to remove + any prior values we may already have */ + STRLIST_FOREACH (list, line, i) { + char *tmp = xstrdup (line); + linep = tmp; + linetok = strsep (&linep, "="); + if (strcmp (linetok, entry) == 0) { + /* We have a match now - to save time we directly replace it */ + free (list[i - 1]); + list[i - 1] = newline; + replaced = true; + free (tmp); + break; + } + free (tmp); + } + + if (! replaced) { + rc_strlist_addsort (&list, newline); + free (newline); + } + free (entry); + } + free (buffer); + fclose (fp); + + return (list); +} +librc_hidden_def(rc_config_load) + +char *rc_config_value (char **list, const char *entry) +{ + char *line; + int i; + char *p; + + STRLIST_FOREACH (list, line, i) { + p = strchr (line, '='); + if (p && strncmp (entry, line, p - line) == 0) + return (p += 1); + } + + return (NULL); +} +librc_hidden_def(rc_config_value) + +char **rc_config_list (const char *file) +{ + FILE *fp; + char *buffer; + char *p; + char *token; + char **list = NULL; + + if (! (fp = fopen (file, "r"))) + return (NULL); + + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + while (fgets (buffer, RC_LINEBUFFER, fp)) { + p = buffer; + + /* Strip leading spaces/tabs */ + while ((*p == ' ') || (*p == '\t')) + p++; + + /* Get entry - we do not want comments */ + token = strsep (&p, "#"); + if (token && (strlen (token) > 1)) { + /* Stip the newline if present */ + if (token[strlen (token) - 1] == '\n') + token[strlen (token) - 1] = 0; + + rc_strlist_add (&list, token); + } + } + free (buffer); + fclose (fp); + + return (list); +} +librc_hidden_def(rc_config_list) diff --git a/src/librc/librc-strlist.c b/src/librc/librc-strlist.c new file mode 100644 index 00000000..815c8370 --- /dev/null +++ b/src/librc/librc-strlist.c @@ -0,0 +1,230 @@ +/* + librc-strlist.h + String list functions for using char ** arrays + + Based on a previous implementation by Martin Schlemmer + */ + +/* + * Copyright 2007 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "librc.h" + +static char *_rc_strlist_add (char ***list, const char *item, bool uniq) +{ + char **newlist; + char **lst = *list; + int i = 0; + + if (! item) + return (NULL); + + while (lst && lst[i]) { + if (uniq && strcmp (lst[i], item) == 0) { + errno = EEXIST; + return (NULL); + } + i++; + } + + newlist = xrealloc (lst, sizeof (char *) * (i + 2)); + newlist[i] = xstrdup (item); + newlist[i + 1] = NULL; + + *list = newlist; + return (newlist[i]); +} + +char *rc_strlist_add (char ***list, const char *item) +{ + return (_rc_strlist_add (list, item, false)); +} +librc_hidden_def(rc_strlist_add) + +char *rc_strlist_addu (char ***list, const char *item) +{ + return (_rc_strlist_add (list, item, true)); +} +librc_hidden_def(rc_strlist_addu) + +static char *_rc_strlist_addsort (char ***list, const char *item, + int (*sortfunc) (const char *s1, + const char *s2), + bool uniq) +{ + char **newlist; + char **lst = *list; + int i = 0; + char *tmp1; + char *tmp2; + char *retval; + + if (! item) + return (NULL); + + while (lst && lst[i]) { + if (uniq && strcmp (lst[i], item) == 0) { + errno = EEXIST; + return (NULL); + } + i++; + } + + newlist = xrealloc (lst, sizeof (char *) * (i + 2)); + + if (! i) + newlist[i] = NULL; + newlist[i + 1] = NULL; + + i = 0; + while (newlist[i] && sortfunc (newlist[i], item) < 0) + i++; + + tmp1 = newlist[i]; + retval = newlist[i] = xstrdup (item); + do { + i++; + tmp2 = newlist[i]; + newlist[i] = tmp1; + tmp1 = tmp2; + } while (tmp1); + + *list = newlist; + return (retval); +} + +char *rc_strlist_addsort (char ***list, const char *item) +{ + return (_rc_strlist_addsort (list, item, strcoll, false)); +} +librc_hidden_def(rc_strlist_addsort) + +char *rc_strlist_addsortc (char ***list, const char *item) +{ + return (_rc_strlist_addsort (list, item, strcmp, false)); +} +librc_hidden_def(rc_strlist_addsortc) + +char *rc_strlist_addsortu (char ***list, const char *item) +{ + return (_rc_strlist_addsort (list, item, strcmp, true)); +} +librc_hidden_def(rc_strlist_addsortu) + +bool rc_strlist_delete (char ***list, const char *item) +{ + char **lst = *list; + int i = 0; + + if (!lst || ! item) + return (false); + + while (lst[i]) { + if (strcmp (lst[i], item) == 0) { + free (lst[i]); + do { + lst[i] = lst[i + 1]; + i++; + } while (lst[i]); + return (true); + } + i++; + } + + errno = ENOENT; + return (false); +} +librc_hidden_def(rc_strlist_delete) + +char *rc_strlist_join (char ***list1, char **list2) +{ + char **lst1 = *list1; + char **newlist; + int i = 0; + int j = 0; + + if (! list2) + return (NULL); + + while (lst1 && lst1[i]) + i++; + + while (list2[j]) + j++; + + newlist = xrealloc (lst1, sizeof (char *) * (i + j + 1)); + + j = 0; + while (list2[j]) { + newlist[i] = list2[j]; + /* Take the item off the 2nd list as it's only a shallow copy */ + list2[j] = NULL; + i++; + j++; + } + newlist[i] = NULL; + + *list1 = newlist; + return (newlist[i == 0 ? 0 : i - 1]); +} +librc_hidden_def(rc_strlist_join) + +void rc_strlist_reverse (char **list) +{ + char *item; + int i = 0; + int j = 0; + + if (! list) + return; + + while (list[j]) + j++; + j--; + + while (i < j && list[i] && list[j]) { + item = list[i]; + list[i] = list[j]; + list[j] = item; + i++; + j--; + } +} +librc_hidden_def(rc_strlist_reverse) + +void rc_strlist_free (char **list) +{ + int i = 0; + + if (! list) + return; + + while (list[i]) + free (list[i++]); + + free (list); +} +librc_hidden_def(rc_strlist_free) diff --git a/src/librc/librc.c b/src/librc/librc.c new file mode 100644 index 00000000..15309f87 --- /dev/null +++ b/src/librc/librc.c @@ -0,0 +1,891 @@ +/* + librc + core RC functions + */ + +/* + * Copyright 2007-2008 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +const char librc_copyright[] = "Copyright (c) 2007-2008 Roy Marples"; + +#include "librc.h" + +#define SOFTLEVEL RC_SVCDIR "/softlevel" + +#ifndef S_IXUGO +# define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH) +#endif + +/* File stream used for plugins to write environ vars to */ +FILE *rc_environ_fd = NULL; + +typedef struct rc_service_state_name { + rc_service_state_t state; + const char *name; +} rc_service_state_name_t; + +/* We MUST list the states below 0x10 first + * The rest can be in any order */ +static const rc_service_state_name_t rc_service_state_names[] = { + { RC_SERVICE_STARTED, "started" }, + { RC_SERVICE_STOPPED, "stopped" }, + { RC_SERVICE_STARTING, "starting" }, + { RC_SERVICE_STOPPING, "stopping" }, + { RC_SERVICE_INACTIVE, "inactive" }, + { RC_SERVICE_WASINACTIVE, "wasinactive" }, + { RC_SERVICE_COLDPLUGGED, "coldplugged" }, + { RC_SERVICE_FAILED, "failed" }, + { RC_SERVICE_SCHEDULED, "scheduled"}, + { 0, NULL} +}; + +#define LS_INITD 0x01 +#define LS_DIR 0x02 +static char **ls_dir (const char *dir, int options) +{ + DIR *dp; + struct dirent *d; + char **list = NULL; + struct stat buf; + + if ((dp = opendir (dir)) == NULL) + return (NULL); + + while (((d = readdir (dp)) != NULL)) { + if (d->d_name[0] != '.') { + if (options & LS_INITD) { + int l = strlen (d->d_name); + + /* Check that our file really exists. + * This is important as a service maybe in a runlevel, but + * could also have been removed. */ + char *file = rc_strcatpaths (dir, d->d_name, NULL); + int ok = stat (file, &buf); + free (file); + if (ok != 0) + continue; + + /* .sh files are not init scripts */ + if (l > 2 && d->d_name[l - 3] == '.' && + d->d_name[l - 2] == 's' && + d->d_name[l - 1] == 'h') + continue; + } + if (options & LS_DIR) { + if (stat (d->d_name, &buf) == 0 && ! S_ISDIR (buf.st_mode)) + continue; + } + rc_strlist_addsort (&list, d->d_name); + } + } + closedir (dp); + + return (list); +} + +static bool rm_dir (const char *pathname, bool top) +{ + DIR *dp; + struct dirent *d; + + if ((dp = opendir (pathname)) == NULL) + return (false); + + errno = 0; + while (((d = readdir (dp)) != NULL) && errno == 0) { + if (strcmp (d->d_name, ".") != 0 && strcmp (d->d_name, "..") != 0) { + char *tmp = rc_strcatpaths (pathname, d->d_name, (char *) NULL); + if (d->d_type == DT_DIR) { + if (! rm_dir (tmp, true)) + { + free (tmp); + closedir (dp); + return (false); + } + } else { + if (unlink (tmp)) { + free (tmp); + closedir (dp); + return (false); + } + } + free (tmp); + } + } + closedir (dp); + + if (top && rmdir (pathname) != 0) + return (false); + + return (true); +} + +static const char *rc_parse_service_state (rc_service_state_t state) +{ + int i; + + for (i = 0; rc_service_state_names[i].name; i++) { + if (rc_service_state_names[i].state == state) + return (rc_service_state_names[i].name); + } + + return (NULL); +} + +bool rc_runlevel_starting (void) +{ + return (exists (RC_STARTING)); +} +librc_hidden_def(rc_runlevel_starting) + +bool rc_runlevel_stopping (void) +{ + return (exists (RC_STOPPING)); +} +librc_hidden_def(rc_runlevel_stopping) + +char **rc_runlevel_list (void) +{ + return (ls_dir (RC_RUNLEVELDIR, LS_DIR)); +} +librc_hidden_def(rc_runlevel_list) + +char *rc_runlevel_get (void) +{ + FILE *fp; + char *runlevel = NULL; + + if ((fp = fopen (SOFTLEVEL, "r"))) { + runlevel = xmalloc (sizeof (char) * PATH_MAX); + if (fgets (runlevel, PATH_MAX, fp)) { + int i = strlen (runlevel) - 1; + if (runlevel[i] == '\n') + runlevel[i] = 0; + } else + *runlevel = '\0'; + fclose (fp); + } + + if (! runlevel || ! *runlevel) { + free (runlevel); + runlevel = xstrdup (RC_LEVEL_SYSINIT); + } + + return (runlevel); +} +librc_hidden_def(rc_runlevel_get) + +bool rc_runlevel_set (const char *runlevel) +{ + FILE *fp = fopen (SOFTLEVEL, "w"); + + if (! fp) + return (false); + fprintf (fp, "%s", runlevel); + fclose (fp); + return (true); +} +librc_hidden_def(rc_runlevel_set) + +bool rc_runlevel_exists (const char *runlevel) +{ + char *path; + struct stat buf; + bool retval = false; + + if (! runlevel) + return (false); + + path = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, (char *) NULL); + if (stat (path, &buf) == 0 && S_ISDIR (buf.st_mode)) + retval = true; + free (path); + return (retval); +} +librc_hidden_def(rc_runlevel_exists) + +/* Resolve a service name to it's full path */ +char *rc_service_resolve (const char *service) +{ + char buffer[PATH_MAX]; + char *file; + int r = 0; + struct stat buf; + + if (! service) + return (NULL); + + if (service[0] == '/') + return (xstrdup (service)); + + file = rc_strcatpaths (RC_SVCDIR, "started", service, (char *) NULL); + if (lstat (file, &buf) || ! S_ISLNK (buf.st_mode)) { + free (file); + file = rc_strcatpaths (RC_SVCDIR, "inactive", service, (char *) NULL); + if (lstat (file, &buf) || ! S_ISLNK (buf.st_mode)) { + free (file); + file = NULL; + } + } + + memset (buffer, 0, sizeof (buffer)); + if (file) { + r = readlink (file, buffer, sizeof (buffer)); + free (file); + if (r > 0) + return (xstrdup (buffer)); + } + snprintf (buffer, sizeof (buffer), RC_INITDIR "/%s", service); + + /* So we don't exist in /etc/init.d - check /usr/local/etc/init.d */ + if (stat (buffer, &buf) != 0) { + snprintf (buffer, sizeof (buffer), RC_INITDIR_LOCAL "/%s", service); + if (stat (buffer, &buf) != 0) + return (NULL); + } + + return (xstrdup (buffer)); +} +librc_hidden_def(rc_service_resolve) + +bool rc_service_exists (const char *service) +{ + char *file; + bool retval = false; + int len; + struct stat buf; + + if (! service) + return (false); + + len = strlen (service); + + /* .sh files are not init scripts */ + if (len > 2 && service[len - 3] == '.' && + service[len - 2] == 's' && + service[len - 1] == 'h') + return (false); + + file = rc_service_resolve (service); + if (stat (file, &buf) == 0 && buf.st_mode & S_IXUGO) + retval = true; + free (file); + return (retval); +} +librc_hidden_def(rc_service_exists) + +#define OPTSTR ". '%s'; echo \"${opts}\"" +char **rc_service_extra_commands (const char *service) +{ + char *svc; + char *cmd = NULL; + char *buffer = NULL; + char **commands = NULL; + char *token; + char *p = buffer; + FILE *fp; + int l; + + if (! (svc = rc_service_resolve (service))) + return (NULL); + + l = strlen (OPTSTR) + strlen (svc) + 1; + cmd = xmalloc (sizeof (char) * l); + snprintf (cmd, l, OPTSTR, svc); + free (svc); + if ((fp = popen (cmd, "r"))) { + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + if (fgets (buffer, RC_LINEBUFFER, fp)) { + if (buffer[strlen (buffer) - 1] == '\n') + buffer[strlen (buffer) - 1] = '\0'; + while ((token = strsep (&p, " "))) + rc_strlist_addsort (&commands, token); + } + pclose (fp); + free (buffer); + } + free (cmd); + return (commands); +} +librc_hidden_def(rc_service_extra_commands) + +#define DESCSTR ". '%s'; echo \"${description%s%s}\"" +char *rc_service_description (const char *service, const char *option) +{ + char *svc; + char *cmd = NULL; + char *buffer; + char *desc = NULL; + FILE *fp; + int i; + int l; + + if (! (svc = rc_service_resolve (service))) + return (NULL); + + if (! option) + option = ""; + + l = strlen (DESCSTR) + strlen (svc) + strlen (option) + 2; + cmd = xmalloc (sizeof (char) * l); + snprintf (cmd, l, DESCSTR, svc, option ? "_" : "", option); + free (svc); + if ((fp = popen (cmd, "r"))) { + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + while (fgets (buffer, RC_LINEBUFFER, fp)) { + if (! desc) { + desc = xmalloc (strlen (buffer) + 1); + *desc = '\0'; + } else { + desc = xrealloc (desc, strlen (desc) + strlen (buffer) + 1); + } + i = strlen (desc); + memcpy (desc + i, buffer, strlen (buffer)); + memset (desc + i + strlen (buffer), 0, 1); + } + free (buffer); + pclose (fp); + } + free (cmd); + return (desc); +} +librc_hidden_def(rc_service_description) + +bool rc_service_in_runlevel (const char *service, const char *runlevel) +{ + char *file; + bool retval; + + if (! runlevel || ! service) + return (false); + + file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename_c (service), + (char *) NULL); + retval = exists (file); + free (file); + + return (retval); +} +librc_hidden_def(rc_service_in_runlevel) + +bool rc_service_mark (const char *service, const rc_service_state_t state) +{ + char *file; + int i = 0; + int skip_state = -1; + const char *base; + char *init = rc_service_resolve (service); + bool skip_wasinactive = false; + + if (! init) + return (false); + + base = basename_c (service); + + if (state != RC_SERVICE_STOPPED) { + if (! exists (init)) { + free (init); + return (false); + } + + file = rc_strcatpaths (RC_SVCDIR, rc_parse_service_state (state), base, + (char *) NULL); + if (exists (file)) + unlink (file); + i = symlink (init, file); + if (i != 0) { + free (file); + free (init); + return (false); + } + + free (file); + skip_state = state; + } + + if (state == RC_SERVICE_COLDPLUGGED || state == RC_SERVICE_FAILED) { + free (init); + return (true); + } + + /* Remove any old states now */ + for (i = 0; rc_service_state_names[i].name; i++) { + int s = rc_service_state_names[i].state; + + if ((s != skip_state && + s != RC_SERVICE_STOPPED && + s != RC_SERVICE_COLDPLUGGED && + s != RC_SERVICE_SCHEDULED) && + (! skip_wasinactive || s != RC_SERVICE_WASINACTIVE)) + { + file = rc_strcatpaths (RC_SVCDIR, rc_parse_service_state (s), base, + (char *) NULL); + if (exists (file)) { + if ((state == RC_SERVICE_STARTING || + state == RC_SERVICE_STOPPING) && + s == RC_SERVICE_INACTIVE) + { + char *wasfile = rc_strcatpaths (RC_SVCDIR, + rc_parse_service_state (RC_SERVICE_WASINACTIVE), + base, (char *) NULL); + + symlink (init, wasfile); + skip_wasinactive = true; + free (wasfile); + } + unlink (file); + } + free (file); + } + } + + /* Remove the exclusive state if we're inactive */ + if (state == RC_SERVICE_STARTED || + state == RC_SERVICE_STOPPED || + state == RC_SERVICE_INACTIVE) + { + file = rc_strcatpaths (RC_SVCDIR, "exclusive", base, (char *) NULL); + unlink (file); + free (file); + } + + /* Remove any options and daemons the service may have stored */ + if (state == RC_SERVICE_STOPPED) { + char *dir = rc_strcatpaths (RC_SVCDIR, "options", base, (char *) NULL); + rm_dir (dir, true); + free (dir); + + dir = rc_strcatpaths (RC_SVCDIR, "daemons", base, (char *) NULL); + rm_dir (dir, true); + free (dir); + + rc_service_schedule_clear (service); + } + + /* These are final states, so remove us from scheduled */ + if (state == RC_SERVICE_STARTED || state == RC_SERVICE_STOPPED) { + char *sdir = rc_strcatpaths (RC_SVCDIR, "scheduled", (char *) NULL); + char **dirs = ls_dir (sdir, 0); + char *dir; + int serrno; + + STRLIST_FOREACH (dirs, dir, i) { + char *bdir = rc_strcatpaths (sdir, dir, (char *) NULL); + file = rc_strcatpaths (bdir, base, (char *) NULL); + unlink (file); + free (file); + + /* Try and remove the dir - we don't care about errors */ + serrno = errno; + rmdir (bdir); + errno = serrno; + free (bdir); + } + rc_strlist_free (dirs); + free (sdir); + } + + free (init); + return (true); +} +librc_hidden_def(rc_service_mark) + +rc_service_state_t rc_service_state (const char *service) +{ + int i; + int state = RC_SERVICE_STOPPED; + + for (i = 0; rc_service_state_names[i].name; i++) { + char *file = rc_strcatpaths (RC_SVCDIR, rc_service_state_names[i].name, + basename_c (service), (char*) NULL); + if (exists (file)) { + if (rc_service_state_names[i].state <= 0x10) + state = rc_service_state_names[i].state; + else + state |= rc_service_state_names[i].state; + } + free (file); + } + + if (state & RC_SERVICE_STOPPED) { + char **services = rc_services_scheduled_by (service); + if (services) { + state |= RC_SERVICE_SCHEDULED; + free (services); + } + } + + return (state); +} +librc_hidden_def(rc_service_state) + +char *rc_service_value_get (const char *service, const char *option) +{ + FILE *fp; + char *buffer = NULL; + char *file = rc_strcatpaths (RC_SVCDIR, "options", service, option, + (char *) NULL); + + if ((fp = fopen (file, "r"))) { + buffer = xmalloc (sizeof (char) * RC_LINEBUFFER); + fgets (buffer, RC_LINEBUFFER, fp); + fclose (fp); + } + free (file); + + return (buffer); +} +librc_hidden_def(rc_service_value_get) + +bool rc_service_value_set (const char *service, const char *option, + const char *value) +{ + FILE *fp; + char *path = rc_strcatpaths (RC_SVCDIR, "options", service, (char *) NULL); + char *file = rc_strcatpaths (path, option, (char *) NULL); + bool retval = false; + + if (mkdir (path, 0755) != 0 && errno != EEXIST) { + free (path); + free (file); + return (false); + } + + if ((fp = fopen (file, "w"))) { + if (value) + fprintf (fp, "%s", value); + fclose (fp); + retval = true; + } + + free (path); + free (file); + return (retval); +} +librc_hidden_def(rc_service_value_set) + +static pid_t _exec_service (const char *service, const char *arg) +{ + char *file; + char *fifo; + pid_t pid = -1; + + file = rc_service_resolve (service); + if (! exists (file)) { + rc_service_mark (service, RC_SERVICE_STOPPED); + free (file); + return (0); + } + + /* We create a fifo so that other services can wait until we complete */ + fifo = rc_strcatpaths (RC_SVCDIR, "exclusive", basename_c (service), + (char *) NULL); + + if (mkfifo (fifo, 0600) != 0 && errno != EEXIST) { + free (fifo); + free (file); + return (-1); + } + + if ((pid = vfork ()) == 0) { + execl (file, file, arg, (char *) NULL); + fprintf (stderr, "unable to exec `%s': %s\n", file, strerror (errno)); + unlink (fifo); + _exit (EXIT_FAILURE); + } + + free (fifo); + free (file); + + if (pid == -1) + fprintf (stderr, "vfork: %s\n", strerror (errno)); + + return (pid); +} + +pid_t rc_service_stop (const char *service) +{ + rc_service_state_t state = rc_service_state (service); + + if (state & RC_SERVICE_FAILED) + return (-1); + + if (state & RC_SERVICE_STOPPED) + return (0); + + return (_exec_service (service, "stop")); +} +librc_hidden_def(rc_service_stop) + +pid_t rc_service_start (const char *service) +{ + rc_service_state_t state = rc_service_state (service); + + if (state & RC_SERVICE_FAILED) + return (-1); + + if (! state & RC_SERVICE_STOPPED) + return (0); + + return (_exec_service (service, "start")); +} +librc_hidden_def(rc_service_start) + +bool rc_service_schedule_start (const char *service, + const char *service_to_start) +{ + char *dir; + char *init; + char *file; + bool retval; + + /* service may be a provided service, like net */ + if (! service || ! rc_service_exists (service_to_start)) + return (false); + + dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename_c (service), + (char *) NULL); + if (mkdir (dir, 0755) != 0 && errno != EEXIST) { + free (dir); + return (false); + } + + init = rc_service_resolve (service_to_start); + file = rc_strcatpaths (dir, basename_c (service_to_start), (char *) NULL); + retval = (exists (file) || symlink (init, file) == 0); + free (init); + free (file); + free (dir); + + return (retval); +} +librc_hidden_def(rc_service_schedule_start) + +bool rc_service_schedule_clear (const char *service) +{ + char *dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename_c (service), + (char *) NULL); + bool retval; + + if (! (retval = rm_dir (dir, true)) && errno == ENOENT) + retval = true; + free (dir); + return (retval); +} +librc_hidden_def(rc_service_schedule_clear) + + +char **rc_services_in_runlevel (const char *runlevel) +{ + char *dir; + char **list = NULL; + + if (! runlevel) { + int i; + char **local = ls_dir (RC_INITDIR_LOCAL, LS_INITD); + + list = ls_dir (RC_INITDIR, LS_INITD); + STRLIST_FOREACH (local, dir, i) + rc_strlist_addsortu (&list, dir); + rc_strlist_free (local); + return (list); + } + + /* These special levels never contain any services */ + if (strcmp (runlevel, RC_LEVEL_SYSINIT) == 0 || + strcmp (runlevel, RC_LEVEL_SINGLE) == 0) + return (NULL); + + dir = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, (char *) NULL); + list = ls_dir (dir, LS_INITD); + free (dir); + return (list); +} +librc_hidden_def(rc_services_in_runlevel) + +char **rc_services_in_state (rc_service_state_t state) +{ + char *dir = rc_strcatpaths (RC_SVCDIR, rc_parse_service_state (state), + (char *) NULL); + char **list = NULL; + + if (state == RC_SERVICE_SCHEDULED) { + char **dirs = ls_dir (dir, 0); + char *d; + int i; + + STRLIST_FOREACH (dirs, d, i) { + char *p = rc_strcatpaths (dir, d, (char *) NULL); + char **entries = ls_dir (p, LS_INITD); + char *e; + int j; + + STRLIST_FOREACH (entries, e, j) + rc_strlist_addsortu (&list, e); + + if (entries) + free (entries); + } + + if (dirs) + free (dirs); + } else { + list = ls_dir (dir, LS_INITD); + } + + free (dir); + return (list); +} +librc_hidden_def(rc_services_in_state) + +bool rc_service_add (const char *runlevel, const char *service) +{ + bool retval; + char *init; + char *file; + + if (! rc_runlevel_exists (runlevel)) { + errno = ENOENT; + return (false); + } + + if (rc_service_in_runlevel (service, runlevel)) { + errno = EEXIST; + return (false); + } + + init = rc_service_resolve (service); + + /* We need to ensure that only things in /etc/init.d are added + * to the boot runlevel */ + if (strcmp (runlevel, RC_LEVEL_BOOT) == 0) { + char *tmp = xstrdup (init); + retval = (strcmp (dirname (tmp), RC_INITDIR) == 0); + free (tmp); + if (! retval) { + free (init); + errno = EPERM; + return (false); + } + } + + file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename_c (service), + (char *) NULL); + retval = (symlink (init, file) == 0); + free (init); + free (file); + return (retval); +} +librc_hidden_def(rc_service_add) + +bool rc_service_delete (const char *runlevel, const char *service) +{ + char *file; + bool retval = false; + + if (! runlevel || ! service) + return (false); + + file = rc_strcatpaths (RC_RUNLEVELDIR, runlevel, basename_c (service), + (char *) NULL); + if (unlink (file) == 0) + retval = true; + + free (file); + return (retval); +} +librc_hidden_def(rc_service_delete) + +char **rc_services_scheduled_by (const char *service) +{ + char **dirs = ls_dir (RC_SVCDIR "/scheduled", 0); + char **list = NULL; + char *dir; + int i; + + STRLIST_FOREACH (dirs, dir, i) { + char *file = rc_strcatpaths (RC_SVCDIR, "scheduled", dir, service, + (char *) NULL); + if (exists (file)) + rc_strlist_add (&list, file); + free (file); + } + rc_strlist_free (dirs); + + return (list); +} +librc_hidden_def(rc_services_scheduled_by) + +char **rc_services_scheduled (const char *service) +{ + char *dir = rc_strcatpaths (RC_SVCDIR, "scheduled", basename_c (service), + (char *) NULL); + char **list = NULL; + + list = ls_dir (dir, LS_INITD); + free (dir); + return (list); +} +librc_hidden_def(rc_services_scheduled) + +bool rc_service_plugable (const char *service) +{ + char *list; + char *p; + char *star; + char *token; + bool allow = true; + char *match = getenv ("RC_PLUG_SERVICES"); + if (! match) + return true; + + list = xstrdup (match); + p = list; + while ((token = strsep (&p, " "))) { + bool truefalse = true; + if (token[0] == '!') { + truefalse = false; + token++; + } + + star = strchr (token, '*'); + if (star) { + if (strncmp (service, token, star - token) == 0) { + allow = truefalse; + break; + } + } else { + if (strcmp (service, token) == 0) { + allow = truefalse; + break; + } + } + } + + free (list); + return (allow); +} +librc_hidden_def(rc_service_plugable) diff --git a/src/librc/librc.h b/src/librc/librc.h new file mode 100644 index 00000000..cf61217a --- /dev/null +++ b/src/librc/librc.h @@ -0,0 +1,127 @@ +/* + * librc.h + * Internal header file to setup build env for files in librc.so + */ + +/* + * Copyright 2007 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _LIBRC_H_ +#define _LIBRC_H_ + +#define _IN_LIBRC + +#include <sys/param.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <limits.h> +#include <regex.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <time.h> +#include <unistd.h> + +#if defined(__DragonFly__) || defined(__FreeBSD__) || \ + defined(__NetBSD__) || defined (__OpenBSD__) +#include <sys/param.h> +#include <sys/user.h> +#include <sys/sysctl.h> +#include <kvm.h> +#endif + +#include "librc-depend.h" +#include "rc.h" +#include "rc-misc.h" +#include "strlist.h" + +#include "hidden-visibility.h" +#define librc_hidden_proto(x) hidden_proto(x) +#define librc_hidden_def(x) hidden_def(x) + +librc_hidden_proto(rc_config_list) +librc_hidden_proto(rc_config_load) +librc_hidden_proto(rc_config_value) +librc_hidden_proto(rc_deptree_depend) +librc_hidden_proto(rc_deptree_depends) +librc_hidden_proto(rc_deptree_free) +librc_hidden_proto(rc_deptree_load) +librc_hidden_proto(rc_deptree_order) +librc_hidden_proto(rc_deptree_update) +librc_hidden_proto(rc_deptree_update_needed) +librc_hidden_proto(rc_find_pids) +librc_hidden_proto(rc_runlevel_exists) +librc_hidden_proto(rc_runlevel_get) +librc_hidden_proto(rc_runlevel_list) +librc_hidden_proto(rc_runlevel_set) +librc_hidden_proto(rc_runlevel_starting) +librc_hidden_proto(rc_runlevel_stopping) +librc_hidden_proto(rc_service_add) +librc_hidden_proto(rc_service_daemons_crashed) +librc_hidden_proto(rc_service_daemon_set) +librc_hidden_proto(rc_service_delete) +librc_hidden_proto(rc_service_description) +librc_hidden_proto(rc_service_exists) +librc_hidden_proto(rc_service_extra_commands) +librc_hidden_proto(rc_service_in_runlevel) +librc_hidden_proto(rc_service_mark) +librc_hidden_proto(rc_service_plugable) +librc_hidden_proto(rc_service_resolve) +librc_hidden_proto(rc_service_schedule_clear) +librc_hidden_proto(rc_service_schedule_start) +librc_hidden_proto(rc_service_start) +librc_hidden_proto(rc_service_stop) +librc_hidden_proto(rc_services_in_runlevel) +librc_hidden_proto(rc_services_in_state) +librc_hidden_proto(rc_services_scheduled) +librc_hidden_proto(rc_services_scheduled_by) +librc_hidden_proto(rc_service_started_daemon) +librc_hidden_proto(rc_service_state) +librc_hidden_proto(rc_service_value_get) +librc_hidden_proto(rc_service_value_set) +librc_hidden_proto(rc_strcatpaths) +librc_hidden_proto(rc_strlist_add) +librc_hidden_proto(rc_strlist_addu) +librc_hidden_proto(rc_strlist_addsort) +librc_hidden_proto(rc_strlist_addsortc) +librc_hidden_proto(rc_strlist_addsortu) +librc_hidden_proto(rc_strlist_delete) +librc_hidden_proto(rc_strlist_free) +librc_hidden_proto(rc_strlist_join) +librc_hidden_proto(rc_strlist_reverse) +librc_hidden_proto(rc_yesno) + +#endif diff --git a/src/librc/rc.h b/src/librc/rc.h new file mode 100644 index 00000000..0020764e --- /dev/null +++ b/src/librc/rc.h @@ -0,0 +1,446 @@ +/* + * Copyright 2007 Roy Marples + * All rights reserved + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __RC_H__ +#define __RC_H__ + +#ifdef __GNUC__ +# define GCC_VERSION (__GNUC__ * 1000 + __GNUC__MINOR) +# if (GCC_VERSION >= 3005) +# define SENTINEL __attribute__ ((__sentinel__)) +# endif +#endif +#ifndef SENTINEL +# define SENTINEL +#endif + +#include <sys/types.h> +#include <stdbool.h> + +/*! @name Reserved runlevel names */ +#define RC_LEVEL_SYSINIT "sysinit" +#define RC_LEVEL_SINGLE "single" +#define RC_LEVEL_SHUTDOWN "shutdown" +#define RC_LEVEL_REBOOT "reboot" + +/*! Return the current runlevel. + * @return the current runlevel */ +char *rc_runlevel_get (void); + +/*! Checks if the runlevel exists or not + * @param runlevel to check + * @return true if the runlevel exists, otherwise false */ +bool rc_runlevel_exists (const char *runlevel); + +/*! Return a NULL terminated list of runlevels + * @return a NULL terminated list of runlevels */ +char **rc_runlevel_list (void); + +/*! Set the runlevel. + * This just changes the stored runlevel and does not start or stop any services. + * @param runlevel to store */ +bool rc_runlevel_set (const char *runlevel); + +/*! Is the runlevel starting? + * @return true if yes, otherwise false */ +bool rc_runlevel_starting (void); + +/*! Is the runlevel stopping? + * @return true if yes, otherwise false */ +bool rc_runlevel_stopping (void); + +/*! @name RC + * A service can be given as a full path or just its name. + * If its just a name then we try to resolve the service to a full path. + * This should allow the use if local init.d directories in the future. */ + +/*! @brief States a service can be in */ +typedef enum +{ + /* These are actual states + * The service has to be in one only at all times */ + RC_SERVICE_STOPPED = 0x0001, + RC_SERVICE_STARTED = 0x0002, + RC_SERVICE_STOPPING = 0x0004, + RC_SERVICE_STARTING = 0x0008, + RC_SERVICE_INACTIVE = 0x0010, + + /* Service may or may not have been coldplugged */ + RC_SERVICE_COLDPLUGGED = 0x0100, + + /* Optional states service could also be in */ + RC_SERVICE_FAILED = 0x0200, + RC_SERVICE_SCHEDULED = 0x0400, + RC_SERVICE_WASINACTIVE = 0x0800 +} rc_service_state_t; + +/*! Add the service to the runlevel + * @param runlevel to add to + * @param service to add + * @return true if successful, otherwise false */ +bool rc_service_add (const char *runlevel, const char *service); + +/*! Remove the service from the runlevel + * @param runlevel to remove from + * @param service to remove + * @return true if sucessful, otherwise false */ +bool rc_service_delete (const char *runlevel, const char *service); + +/*! Save the arguments to find a running daemon + * @param service to save arguments for + * @param exec that we started + * @param name of the process (optional) + * @param pidfile of the process (optional) + * @param started if true, add the arguments otherwise remove existing matching arguments */ +bool rc_service_daemon_set (const char *service, const char *exec, + const char *name, const char *pidfile, + bool started); + +/*! Returns a description of what the service and/or option does. + * @param service to check + * @param option to check (if NULL, service description) + * @return a newly allocated pointer to the description */ +char *rc_service_description (const char *service, const char *option); + +/*! Checks if a service exists or not. + * @param service to check + * @return true if service exists, otherwise false */ +bool rc_service_exists (const char *service); + +/*! Checks if a service is in a runlevel + * @param service to check + * @param runlevel it should be in + * @return true if service is in the runlevel, otherwise false */ +bool rc_service_in_runlevel (const char *service, const char *runlevel); + +/*! Marks the service state + * @param service to mark + * @param state service should be in + * @return true if service state change was successful, otherwise false */ +bool rc_service_mark (const char *service, rc_service_state_t state); + +/*! Lists the extra commands a service has + * @param service to load the commands from + * @return NULL terminated string list of commands */ +char **rc_service_extra_commands (const char *service); + +/*! Check if the service is allowed to be hot/cold plugged + * @param service to check + * @return true if allowed, otherwise false */ +bool rc_service_plugable (const char *service); + +/*! Resolves a service name to its full path. + * @param service to check + * @return pointer to full path of service */ +char *rc_service_resolve (const char *service); + +/*! Schedule a service to be started when another service starts + * @param service that starts the scheduled service when started + * @param service_to_start service that will be started */ +bool rc_service_schedule_start (const char *service, + const char *service_to_start); +/*! Return a NULL terminated list of services that are scheduled to start + * when the given service has started + * @param service to check + * @return NULL terminated list of services scheduled to start */ +char **rc_services_scheduled_by (const char *service); + +/*! Clear the list of services scheduled to be started by this service + * @param service to clear + * @return true if no errors, otherwise false */ +bool rc_service_schedule_clear (const char *service); + +/*! Checks if a service in in a state + * @param service to check + * @return state of the service */ +rc_service_state_t rc_service_state (const char *service); + +/*! Start a service + * @param service to start + * @return pid of the service starting process */ +pid_t rc_service_start (const char *service); + +/*! Stop a service + * @param service to stop + * @return pid of service stopping process */ +pid_t rc_service_stop (const char *service); + +/*! Check if the service started the daemon + * @param service to check + * @param exec to check + * @param indx of the daemon (optional - 1st daemon, 2nd daemon, etc) + * @return true if started by this service, otherwise false */ +bool rc_service_started_daemon (const char *service, const char *exec, + int indx); + +/*! Return a saved value for a service + * @param service to check + * @param option to load + * @return saved value */ +char *rc_service_value_get (const char *service, const char *option); + +/*! Save a persistent value for a service + * @param service to save for + * @param option to save + * @param value of the option + * @return true if saved, otherwise false */ +bool rc_service_value_set (const char *service, const char *option, + const char *value); + +/*! List the services in a runlevel + * @param runlevel to list + * @return NULL terminated list of services */ +char **rc_services_in_runlevel (const char *runlevel); + +/*! List the services in a state + * @param state to list + * @return NULL terminated list of services */ +char **rc_services_in_state (rc_service_state_t state); + +/*! List the services shceduled to start when this one does + * @param service to check + * @return NULL terminated list of services */ +char **rc_services_scheduled (const char *service); + +/*! Checks that all daemons started with start-stop-daemon by the service + * are still running. + * @param service to check + * @return true if all daemons started are still running, otherwise false */ +bool rc_service_daemons_crashed (const char *service); + +/*! @name Dependency options + * These options can change the services found by the rc_get_depinfo and + * rc_get_depends functions. */ +/*! Trace provided services */ +#define RC_DEP_TRACE 0x01 +/*! Only use services added to runlevels */ +#define RC_DEP_STRICT 0x02 +/*! Runlevel is starting */ +#define RC_DEP_START 0x04 +/*! Runlevel is stopping */ +#define RC_DEP_STOP 0x08 + +/*! @name Dependencies + * We analyse each init script and cache the resultant dependency tree. + * This tree can be accessed using the below functions. */ + +#ifndef _IN_LIBRC +/* Handles to internal structures */ +typedef void *rc_depinfo_t; +#endif + +/*! Update the cached dependency tree if it's older than any init script, + * its configuration file or an external configuration file the init script + * has specified. + * @return true if successful, otherwise false */ +bool rc_deptree_update (void); + +/*! Check if the cached dependency tree is older than any init script, + * its configuration file or an external configuration file the init script + * has specified. + * @return true if it needs updating, otherwise false */ +bool rc_deptree_update_needed (void); + +/*! Load the cached dependency tree and return a pointer to it. + * This pointer should be freed with rc_deptree_free when done. + * @return pointer to the dependency tree */ +rc_depinfo_t *rc_deptree_load (void); + +/*! List the depend for the type of service + * @param deptree to search + * @param type to use (keywords, etc) + * @param service to check + * @return NULL terminated list of services in order */ +char **rc_deptree_depend (const rc_depinfo_t *deptree, + const char *type, const char *service); + +/*! List all the services in order that the given services have + * for the given types and options. + * @param deptree to search + * @param types to use (ineed, iuse, etc) + * @param services to check + * @param options to pass + * @return NULL terminated list of services in order */ +char **rc_deptree_depends (const rc_depinfo_t *deptree, + const char *const *types, + const char *const *services, const char *runlevel, + int options); + +/*! List all the services that should be stoppned and then started, in order, + * for the given runlevel, including sysinit and boot services where + * approriate. + * @param deptree to search + * @param runlevel to change into + * @param options to pass + * @return NULL terminated list of services in order */ +char **rc_deptree_order (const rc_depinfo_t *deptree, const char *runlevel, + int options); + +/*! Free a deptree and its information + * @param deptree to free */ +void rc_deptree_free (rc_depinfo_t *deptree); + +/*! @name Plugins + * For each plugin loaded we will call rc_plugin_hook with the below + * enum and either the runlevel name or service name. + * + * Plugins are called when rc does something. This does not indicate an + * end result and the plugin should use the above functions to query things + * like service status. + * + * The service hooks have extra ones - now and done. This is because after + * start_in we may start other services before we start the service in + * question. now shows we really will start the service now and done shows + * when we have done it as may start scheduled services at this point. */ +/*! Points at which a plugin can hook into RC */ +typedef enum +{ + RC_HOOK_RUNLEVEL_STOP_IN = 1, + RC_HOOK_RUNLEVEL_STOP_OUT = 4, + RC_HOOK_RUNLEVEL_START_IN = 5, + RC_HOOK_RUNLEVEL_START_OUT = 8, + /*! We send the abort if an init script requests we abort and drop + * into single user mode if system not fully booted */ + RC_HOOK_ABORT = 99, + RC_HOOK_SERVICE_STOP_IN = 101, + RC_HOOK_SERVICE_STOP_NOW = 102, + RC_HOOK_SERVICE_STOP_DONE = 103, + RC_HOOK_SERVICE_STOP_OUT = 104, + RC_HOOK_SERVICE_START_IN = 105, + RC_HOOK_SERVICE_START_NOW = 106, + RC_HOOK_SERVICE_START_DONE = 107, + RC_HOOK_SERVICE_START_OUT = 108 +} rc_hook_t; + +/*! Plugin entry point + * @param hook point + * @param name of runlevel or service + * @return 0 for success otherwise -1 */ +int rc_plugin_hook (rc_hook_t hook, const char *name); + +/*! Plugins should write FOO=BAR to this fd to set any environment + * variables they wish. Variables should be separated by NULLs. */ +extern FILE *rc_environ_fd; + +/*! @name Configuration + * These functions help to deal with shell based configuration files */ +/*! Return a NULL terminated list of non comment lines from a file. */ +char **rc_config_list (const char *file); + +/*! Return a NULL terminated list of key=value lines from a file. */ +char **rc_config_load (const char *file); + +/*! Return the value of the entry from a key=value list. */ +char *rc_config_value (char **list, const char *entry); + +/*! Check if a variable is a boolean and return it's value. + * If variable is not a boolean then we set errno to be ENOENT when it does + * not exist or EINVAL if it's not a boolean. + * @param variable to check + * @return true if it matches true, yes or 1, false if otherwise. */ +bool rc_yesno (const char *variable); + +/*! @name String List functions + * Handy functions for dealing with string arrays of char **. + * It's safe to assume that any function here that uses char ** is a string + * list that can be manipulated with the below functions. Every string list + * should be released with a call to rc_strlist_free. */ + +/*! Duplicate the item, add it to end of the list and return a pointer to it. + * @param list to add the item too + * @param item to add. + * @return pointer to newly added item */ +char *rc_strlist_add (char ***list, const char *item); + +/*! If the item does not exist in the list, duplicate it, add it to the + * list and then return a pointer to it. + * @param list to add the item too + * @param item to add. + * @return pointer to newly added item */ +char *rc_strlist_addu (char ***list, const char *item); + +/*! Duplicate the item, add it to the list at the point based on locale and + * then return a pointer to it. + * @param list to add the item too + * @param item to add. + * @return pointer to newly added item */ +char *rc_strlist_addsort (char ***list, const char *item); + +/*! Duplicate the item, add it to the list at the point based on C locale and + * then return a pointer to it. + * @param list to add the item too + * @param item to add. + * @return pointer to newly added item */ +char *rc_strlist_addsortc (char ***list, const char *item); + +/*! If the item does not exist in the list, duplicate it, add it to the + * list based on locale and then return a pointer to it. + * @param list to add the item too + * @param item to add. + * @return pointer to newly added item */ +char *rc_strlist_addsortu (char ***list, const char *item); + +/*! Free the item and remove it from the list. Return 0 on success otherwise -1. + * @param list to add the item too + * @param item to add. + * @return true on success, otherwise false */ +bool rc_strlist_delete (char ***list, const char *item); + +/*! Moves the contents of list2 onto list1, so list2 is effectively emptied. + * Returns a pointer to the last item on the new list. + * @param list1 to append to + * @param list2 to move from + * @return pointer to the last item on the list */ +char *rc_strlist_join (char ***list1, char **list2); + +/*! Reverses the contents of the list. + * @param list to reverse */ +void rc_strlist_reverse (char **list); + +/*! Frees each item on the list and the list itself. + * @param list to free */ +void rc_strlist_free (char **list); + +/*! Concatenate paths adding '/' if needed. The resultant pointer should be + * freed when finished with. + * @param path1 starting path + * @param paths NULL terminated list of paths to add + * @return pointer to the new path */ +char *rc_strcatpaths (const char *path1, const char *paths, ...) SENTINEL; + +/*! Find processes based on criteria. + * All of these are optional. + * pid overrides anything else. + * If both exec and cmd are given then we ignore exec. + * @param exec to check for + * @param cmd to check for + * @param uid to check for + * @param pid to check for + * @return NULL terminated list of pids */ +pid_t *rc_find_pids (const char *exec, const char *cmd, + uid_t uid, pid_t pid); + +#endif |