diff options
Diffstat (limited to 'usr.sbin/jail/state.c')
-rw-r--r-- | usr.sbin/jail/state.c | 466 |
1 files changed, 466 insertions, 0 deletions
diff --git a/usr.sbin/jail/state.c b/usr.sbin/jail/state.c new file mode 100644 index 0000000..159afbc --- /dev/null +++ b/usr.sbin/jail/state.c @@ -0,0 +1,466 @@ +/*- + * Copyright (c) 2010 James Gritton + * 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 <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/uio.h> + +#include <err.h> +#include <stdlib.h> +#include <string.h> + +#include "jailp.h" + +struct cfjails ready = TAILQ_HEAD_INITIALIZER(ready); +struct cfjails waiting = TAILQ_HEAD_INITIALIZER(waiting); + +static void dep_add(struct cfjail *from, struct cfjail *to, unsigned flags); +static int cmp_jailptr(const void *a, const void *b); +static int cmp_jailptr_name(const void *a, const void *b); +static struct cfjail *find_jail(const char *name); +static int running_jid(const char *name, int flags); + +static struct cfjail **jails_byname; +static size_t njails; + +/* + * Set up jail dependency lists. + */ +void +dep_setup(int docf) +{ + struct cfjail *j, *dj; + struct cfparam *p; + struct cfstring *s; + struct cfdepend *d; + const char *cs; + char *pname; + size_t plen; + int error, deps, ldeps; + + if (!docf) { + /* + * With no config file, let "depend" for a single jail + * look at currently running jails. + */ + if ((j = TAILQ_FIRST(&cfjails)) && + (p = j->intparams[IP_DEPEND])) { + STAILQ_FOREACH(s, &p->val, tq) { + if (running_jid(s->s, 0) < 0) { + warnx("depends on nonexistent jail " + "\"%s\"", s->s); + j->flags |= JF_FAILED; + } + } + } + return; + } + + njails = 0; + TAILQ_FOREACH(j, &cfjails, tq) + njails++; + jails_byname = emalloc(njails * sizeof(struct cfjail *)); + njails = 0; + TAILQ_FOREACH(j, &cfjails, tq) + jails_byname[njails++] = j; + qsort(jails_byname, njails, sizeof(struct cfjail *), cmp_jailptr); + error = 0; + deps = 0; + ldeps = 0; + plen = 0; + pname = NULL; + TAILQ_FOREACH(j, &cfjails, tq) { + if (j->flags & JF_FAILED) + continue; + if ((p = j->intparams[IP_DEPEND])) { + STAILQ_FOREACH(s, &p->val, tq) { + dj = find_jail(s->s); + if (dj != NULL) { + deps++; + dep_add(j, dj, 0); + } else { + jail_warnx(j, + "depends on undefined jail \"%s\"", + s->s); + j->flags |= JF_FAILED; + } + } + } + /* A jail has an implied dependency on its parent. */ + if ((cs = strrchr(j->name, '.'))) + { + if (plen < (size_t)(cs - j->name + 1)) { + plen = (cs - j->name) + 1; + pname = erealloc(pname, plen); + } + strlcpy(pname, j->name, plen); + dj = find_jail(pname); + if (dj != NULL) { + ldeps++; + dep_add(j, dj, DF_LIGHT); + } + } + } + + /* Look for dependency loops. */ + if (deps && (deps > 1 || ldeps)) { + (void)start_state(NULL, 0, 0); + while ((j = TAILQ_FIRST(&ready))) { + requeue(j, &cfjails); + dep_done(j, DF_NOFAIL); + } + while ((j = TAILQ_FIRST(&waiting)) != NULL) { + jail_warnx(j, "dependency loop"); + j->flags |= JF_FAILED; + do { + requeue(j, &cfjails); + dep_done(j, DF_NOFAIL); + } while ((j = TAILQ_FIRST(&ready))); + } + TAILQ_FOREACH(j, &cfjails, tq) + STAILQ_FOREACH(d, &j->dep[DEP_FROM], tq[DEP_FROM]) + d->flags &= ~DF_SEEN; + } + if (pname != NULL) + free(pname); +} + +/* + * Return if a jail has dependencies. + */ +int +dep_check(struct cfjail *j) +{ + int reset, depfrom, depto, ndeps, rev; + struct cfjail *dj; + struct cfdepend *d; + + static int bits[] = { 0, 1, 1, 2, 1, 2, 2, 3 }; + + if (j->ndeps == 0) + return 0; + ndeps = 0; + if ((rev = JF_DO_STOP(j->flags))) { + depfrom = DEP_TO; + depto = DEP_FROM; + } else { + depfrom = DEP_FROM; + depto = DEP_TO; + } + STAILQ_FOREACH(d, &j->dep[depfrom], tq[depfrom]) { + if (d->flags & DF_SEEN) + continue; + dj = d->j[depto]; + if (dj->flags & JF_FAILED) { + if (!(j->flags & (JF_DEPEND | JF_FAILED)) && + verbose >= 0) + jail_warnx(j, "skipped"); + j->flags |= JF_FAILED; + continue; + } + /* + * The dependee's state may be set (or changed) as a result of + * being in a dependency it wasn't in earlier. + */ + reset = 0; + if (bits[dj->flags & JF_OP_MASK] <= 1) { + if (!(dj->flags & JF_OP_MASK)) { + reset = 1; + dj->flags |= JF_DEPEND; + requeue(dj, &ready); + } + /* Set or change the dependee's state. */ + switch (j->flags & JF_OP_MASK) { + case JF_START: + dj->flags |= JF_START; + break; + case JF_SET: + if (!(dj->flags & JF_OP_MASK)) + dj->flags |= JF_SET; + else if (dj->flags & JF_STOP) + dj->flags |= JF_START; + break; + case JF_STOP: + case JF_RESTART: + if (!(dj->flags & JF_STOP)) + reset = 1; + dj->flags |= JF_STOP; + if (dj->flags & JF_SET) + dj->flags ^= (JF_START | JF_SET); + break; + } + } + if (reset) + dep_reset(dj); + if (!((d->flags & DF_LIGHT) && + (rev ? dj->jid < 0 : dj->jid > 0))) + ndeps++; + } + if (ndeps == 0) + return 0; + requeue(j, &waiting); + return 1; +} + +/* + * Resolve any dependencies from a finished jail. + */ +void +dep_done(struct cfjail *j, unsigned flags) +{ + struct cfjail *dj; + struct cfdepend *d; + int depfrom, depto; + + if (JF_DO_STOP(j->flags)) { + depfrom = DEP_TO; + depto = DEP_FROM; + } else { + depfrom = DEP_FROM; + depto = DEP_TO; + } + STAILQ_FOREACH(d, &j->dep[depto], tq[depto]) { + if ((d->flags & DF_SEEN) | (flags & ~d->flags & DF_LIGHT)) + continue; + d->flags |= DF_SEEN; + dj = d->j[depfrom]; + if (!(flags & DF_NOFAIL) && (j->flags & JF_FAILED) && + (j->flags & (JF_OP_MASK | JF_DEPEND)) != + (JF_SET | JF_DEPEND)) { + if (!(dj->flags & (JF_DEPEND | JF_FAILED)) && + verbose >= 0) + jail_warnx(dj, "skipped"); + dj->flags |= JF_FAILED; + } + if (!--dj->ndeps && dj->queue == &waiting) + requeue(dj, &ready); + } +} + +/* + * Count a jail's dependencies and mark them as unseen. + */ +void +dep_reset(struct cfjail *j) +{ + int depfrom; + struct cfdepend *d; + + depfrom = JF_DO_STOP(j->flags) ? DEP_TO : DEP_FROM; + j->ndeps = 0; + STAILQ_FOREACH(d, &j->dep[depfrom], tq[depfrom]) + j->ndeps++; +} + +/* + * Find the next jail ready to do something. + */ +struct cfjail * +next_jail(void) +{ + struct cfjail *j; + + if (!(j = next_proc(!TAILQ_EMPTY(&ready))) && + (j = TAILQ_FIRST(&ready)) && JF_DO_STOP(j->flags) && + (j = TAILQ_LAST(&ready, cfjails)) && !JF_DO_STOP(j->flags)) { + TAILQ_FOREACH_REVERSE(j, &ready, cfjails, tq) + if (JF_DO_STOP(j->flags)) + break; + } + if (j != NULL) + requeue(j, &cfjails); + return j; +} + +/* + * Set jails to the proper start state. + */ +int +start_state(const char *target, unsigned state, int running) +{ + struct iovec jiov[6]; + struct cfjail *j, *tj; + int jid; + char namebuf[MAXHOSTNAMELEN]; + + if (!target || (!running && !strcmp(target, "*"))) { + /* + * If there's no target specified, set the state on all jails, + * and start with those that have no dependencies. + */ + TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) { + j->flags = (j->flags & JF_FAILED) | state | JF_WILD; + dep_reset(j); + requeue(j, j->ndeps ? &waiting : &ready); + } + } else if (wild_jail_name(target)) { + /* + * For targets specified singly, or with a non-global wildcard, + * set their state and call them ready (even if there are + * dependencies). Leave everything else unqueued for now. + */ + if (running) { + /* + * -R matches its wildcards against currently running + * jails, not against the config file. + */ + *(const void **)&jiov[0].iov_base = "lastjid"; + jiov[0].iov_len = sizeof("lastjid"); + jiov[1].iov_base = &jid; + jiov[1].iov_len = sizeof(jid); + *(const void **)&jiov[2].iov_base = "jid"; + jiov[2].iov_len = sizeof("jid"); + jiov[3].iov_base = &jid; + jiov[3].iov_len = sizeof(jid); + *(const void **)&jiov[4].iov_base = "name"; + jiov[4].iov_len = sizeof("name"); + jiov[5].iov_base = &namebuf; + jiov[5].iov_len = sizeof(namebuf); + for (jid = 0; jail_get(jiov, 6, 0) > 0; ) { + if (wild_jail_match(namebuf, target)) { + j = add_jail(); + j->name = estrdup(namebuf); + j->jid = jid; + j->flags = (j->flags & JF_FAILED) | + state | JF_WILD; + dep_reset(j); + requeue(j, &ready); + } + } + } else { + TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) { + if (wild_jail_match(j->name, target)) { + j->flags = (j->flags & JF_FAILED) | + state | JF_WILD; + dep_reset(j); + requeue(j, &ready); + } + } + } + } else { + j = find_jail(target); + if (j == NULL && state == JF_STOP) { + /* Allow -[rR] to specify a currently running jail. */ + if ((jid = running_jid(target, JAIL_DYING)) > 0) { + j = add_jail(); + j->name = estrdup(target); + j->jid = jid; + } + } + if (j == NULL) { + warnx("\"%s\" not found", target); + return -1; + } + j->flags = (j->flags & JF_FAILED) | state; + dep_reset(j); + requeue(j, &ready); + } + return 0; +} + +/* + * Move a jail to a new list. + */ +void +requeue(struct cfjail *j, struct cfjails *queue) +{ + if (j->queue != queue) { + TAILQ_REMOVE(j->queue, j, tq); + TAILQ_INSERT_TAIL(queue, j, tq); + j->queue = queue; + } +} + +/* + * Add a dependency edge between two jails. + */ +static void +dep_add(struct cfjail *from, struct cfjail *to, unsigned flags) +{ + struct cfdepend *d; + + d = emalloc(sizeof(struct cfdepend)); + d->flags = flags; + d->j[DEP_FROM] = from; + d->j[DEP_TO] = to; + STAILQ_INSERT_TAIL(&from->dep[DEP_FROM], d, tq[DEP_FROM]); + STAILQ_INSERT_TAIL(&to->dep[DEP_TO], d, tq[DEP_TO]); +} + +/* + * Compare jail pointers for qsort/bsearch. + */ +static int +cmp_jailptr(const void *a, const void *b) +{ + return strcmp((*((struct cfjail * const *)a))->name, + ((*(struct cfjail * const *)b))->name); +} + +static int +cmp_jailptr_name(const void *a, const void *b) +{ + return strcmp((const char *)a, ((*(struct cfjail * const *)b))->name); +} + +/* + * Find a jail object by name. + */ +static struct cfjail * +find_jail(const char *name) +{ + struct cfjail **jp; + + jp = bsearch(name, jails_byname, njails, sizeof(struct cfjail *), + cmp_jailptr_name); + return jp ? *jp : NULL; +} + +/* + * Return the named jail's jid if it is running, and -1 if it isn't. + */ +static int +running_jid(const char *name, int flags) +{ + struct iovec jiov[2]; + char *ep; + int jid; + + if ((jid = strtol(name, &ep, 10)) && !*ep) { + *(const void **)&jiov[0].iov_base = "jid"; + jiov[0].iov_len = sizeof("jid"); + jiov[1].iov_base = &jid; + jiov[1].iov_len = sizeof(jid); + } else { + *(const void **)&jiov[0].iov_base = "name"; + jiov[0].iov_len = sizeof("name"); + jiov[1].iov_len = strlen(name) + 1; + jiov[1].iov_base = alloca(jiov[1].iov_len); + strcpy(jiov[1].iov_base, name); + } + return jail_get(jiov, 2, flags); +} |