diff options
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 |