mtree.c revision 247043
12893Sdfr/*-
22893Sdfr * Copyright (c) 2011 Marcel Moolenaar
32893Sdfr * All rights reserved.
42893Sdfr *
52893Sdfr * Redistribution and use in source and binary forms, with or without
62893Sdfr * modification, are permitted provided that the following conditions
72893Sdfr * are met:
82893Sdfr * 1. Redistributions of source code must retain the above copyright
92893Sdfr *    notice, this list of conditions and the following disclaimer.
102893Sdfr * 2. Redistributions in binary form must reproduce the above copyright
112893Sdfr *    notice, this list of conditions and the following disclaimer in the
122893Sdfr *    documentation and/or other materials provided with the distribution.
132893Sdfr *
142893Sdfr * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
152893Sdfr * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
162893Sdfr * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
172893Sdfr * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
182893Sdfr * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
192893Sdfr * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
202893Sdfr * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
212893Sdfr * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
222893Sdfr * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
232893Sdfr * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
242893Sdfr */
252893Sdfr
262893Sdfr#include <sys/cdefs.h>
272893Sdfr__FBSDID("$FreeBSD: head/usr.sbin/makefs/mtree.c 247043 2013-02-20 15:28:40Z brooks $");
282893Sdfr
292893Sdfr#include <sys/param.h>
302893Sdfr#include <sys/queue.h>
312893Sdfr#include <sys/sbuf.h>
322893Sdfr#include <sys/stat.h>
332893Sdfr#include <sys/types.h>
342893Sdfr#include <assert.h>
352893Sdfr#include <errno.h>
362893Sdfr#include <fcntl.h>
372893Sdfr#include <grp.h>
382893Sdfr#include <inttypes.h>
392893Sdfr#include <pwd.h>
402893Sdfr#include <stdarg.h>
412893Sdfr#include <stdbool.h>
422893Sdfr#include <stddef.h>
432893Sdfr#include <stdio.h>
442893Sdfr#include <stdlib.h>
452893Sdfr#include <string.h>
462893Sdfr#include <strings.h>
472893Sdfr#include <unistd.h>
482893Sdfr
492893Sdfr#include "makefs.h"
502893Sdfr
512893Sdfr#define	IS_DOT(nm)	((nm)[0] == '.' && (nm)[1] == '\0')
522893Sdfr#define	IS_DOTDOT(nm)	((nm)[0] == '.' && (nm)[1] == '.' && (nm)[2] == '\0')
532893Sdfr
542893Sdfrstruct mtree_fileinfo {
552893Sdfr	SLIST_ENTRY(mtree_fileinfo) next;
562893Sdfr	FILE *fp;
572893Sdfr	const char *name;
582893Sdfr	u_int line;
592893Sdfr};
602893Sdfr
612893Sdfr/* Global state used while parsing. */
622893Sdfrstatic SLIST_HEAD(, mtree_fileinfo) mtree_fileinfo =
632893Sdfr    SLIST_HEAD_INITIALIZER(mtree_fileinfo);
642893Sdfrstatic fsnode *mtree_root;
652893Sdfrstatic fsnode *mtree_current;
662893Sdfrstatic fsnode mtree_global;
672893Sdfrstatic fsinode mtree_global_inode;
682893Sdfrstatic u_int errors, warnings;
692893Sdfr
702893Sdfrstatic void mtree_error(const char *, ...) __printflike(1, 2);
712893Sdfrstatic void mtree_warning(const char *, ...) __printflike(1, 2);
722893Sdfr
732893Sdfrstatic int
742893Sdfrmtree_file_push(const char *name, FILE *fp)
752893Sdfr{
762893Sdfr	struct mtree_fileinfo *fi;
772893Sdfr
782893Sdfr	fi = malloc(sizeof(*fi));
792893Sdfr	if (fi == NULL)
802893Sdfr		return (ENOMEM);
812893Sdfr
822893Sdfr	if (strcmp(name, "-") == 0)
832893Sdfr		fi->name = strdup("(stdin)");
842893Sdfr	else
852893Sdfr		fi->name = strdup(name);
862893Sdfr	if (fi->name == NULL) {
872893Sdfr		free(fi);
882893Sdfr		return (ENOMEM);
892893Sdfr	}
902893Sdfr
912893Sdfr	fi->fp = fp;
922893Sdfr	fi->line = 0;
932893Sdfr
942893Sdfr	SLIST_INSERT_HEAD(&mtree_fileinfo, fi, next);
952893Sdfr	return (0);
962893Sdfr}
972893Sdfr
982893Sdfrstatic void
992893Sdfrmtree_print(const char *msgtype, const char *fmt, va_list ap)
1002893Sdfr{
1012893Sdfr	struct mtree_fileinfo *fi;
1022893Sdfr
1032893Sdfr	if (msgtype != NULL) {
1042893Sdfr		fi = SLIST_FIRST(&mtree_fileinfo);
1052893Sdfr		if (fi != NULL)
1062893Sdfr			fprintf(stderr, "%s:%u: ", fi->name, fi->line);
1072893Sdfr		fprintf(stderr, "%s: ", msgtype);
1082893Sdfr	}
1092893Sdfr	vfprintf(stderr, fmt, ap);
1102893Sdfr}
1112893Sdfr
1122893Sdfrstatic void
1132893Sdfrmtree_error(const char *fmt, ...)
1142893Sdfr{
1152893Sdfr	va_list ap;
1162893Sdfr
1172893Sdfr	va_start(ap, fmt);
1182893Sdfr	mtree_print("error", fmt, ap);
1192893Sdfr	va_end(ap);
1202893Sdfr
1212893Sdfr	errors++;
1222893Sdfr	fputc('\n', stderr);
1232893Sdfr}
1242893Sdfr
1252893Sdfrstatic void
1262893Sdfrmtree_warning(const char *fmt, ...)
1272893Sdfr{
1282893Sdfr	va_list ap;
1292893Sdfr
1302893Sdfr	va_start(ap, fmt);
1312893Sdfr	mtree_print("warning", fmt, ap);
1322893Sdfr	va_end(ap);
1332893Sdfr
1342893Sdfr	warnings++;
1352893Sdfr	fputc('\n', stderr);
1362893Sdfr}
1372893Sdfr
1382893Sdfr#ifndef MAKEFS_MAX_TREE_DEPTH
1392893Sdfr# define MAKEFS_MAX_TREE_DEPTH (MAXPATHLEN/2)
1402893Sdfr#endif
1412893Sdfr
1422893Sdfr/* construct path to node->name */
1432893Sdfrstatic char *
1442893Sdfrmtree_file_path(fsnode *node)
1452893Sdfr{
1462893Sdfr	fsnode *pnode;
1472893Sdfr	struct sbuf *sb;
1482893Sdfr	char *res, *rp[MAKEFS_MAX_TREE_DEPTH];
1492893Sdfr	int depth;
1502893Sdfr
1512893Sdfr	depth = 0;
1522893Sdfr	rp[depth] = node->name;
1532893Sdfr	for (pnode = node->parent; pnode && depth < MAKEFS_MAX_TREE_DEPTH;
1542893Sdfr	     pnode = pnode->parent) {
1552893Sdfr		if (strcmp(pnode->name, ".") == 0)
1562893Sdfr			break;
1572893Sdfr		rp[++depth] = pnode->name;
1582893Sdfr	}
1592893Sdfr
1602893Sdfr	sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND);
1612893Sdfr	if (sb == NULL) {
1622893Sdfr		errno = ENOMEM;
1632893Sdfr		return (NULL);
1642893Sdfr	}
1652893Sdfr	while (depth > 0) {
1662893Sdfr		sbuf_cat(sb, rp[depth--]);
1672893Sdfr		sbuf_putc(sb, '/');
1682893Sdfr	}
1692893Sdfr	sbuf_cat(sb, rp[depth]);
1702893Sdfr	sbuf_finish(sb);
1712893Sdfr	res = strdup(sbuf_data(sb));
1722893Sdfr	sbuf_delete(sb);
1732893Sdfr	if (res == NULL)
1742893Sdfr		errno = ENOMEM;
1752893Sdfr	return res;
1762893Sdfr
1772893Sdfr}
1782893Sdfr
1792893Sdfr/* mtree_resolve() sets errno to indicate why NULL was returned. */
1802893Sdfrstatic char *
1812893Sdfrmtree_resolve(const char *spec, int *istemp)
1822893Sdfr{
1832893Sdfr	struct sbuf *sb;
1842893Sdfr	char *res, *var;
1852893Sdfr	const char *base, *p, *v;
1862893Sdfr	size_t len;
1872893Sdfr	int c, error, quoted, subst;
1882893Sdfr
1892893Sdfr	len = strlen(spec);
1902893Sdfr	if (len == 0) {
1912893Sdfr		errno = EINVAL;
1922893Sdfr		return (NULL);
1932893Sdfr	}
1942893Sdfr
1952893Sdfr	c = (len > 1) ? (spec[0] == spec[len - 1]) ? spec[0] : 0 : 0;
1962893Sdfr	*istemp = (c == '`') ? 1 : 0;
1972893Sdfr	subst = (c == '`' || c == '"') ? 1 : 0;
1982893Sdfr	quoted = (subst || c == '\'') ? 1 : 0;
1992893Sdfr
2002893Sdfr	if (!subst) {
2012893Sdfr		res = strdup(spec + quoted);
2022893Sdfr		if (res != NULL && quoted)
2032893Sdfr			res[len - 2] = '\0';
2042893Sdfr		return (res);
2052893Sdfr	}
2062893Sdfr
2072893Sdfr	sb = sbuf_new_auto();
2082893Sdfr	if (sb == NULL) {
2092893Sdfr		errno = ENOMEM;
2102893Sdfr		return (NULL);
2112893Sdfr	}
2122893Sdfr
2132893Sdfr	base = spec + 1;
2142893Sdfr	len -= 2;
2152893Sdfr	error = 0;
2162893Sdfr	while (len > 0) {
2172893Sdfr		p = strchr(base, '$');
2182893Sdfr		if (p == NULL) {
2192893Sdfr			sbuf_bcat(sb, base, len);
2202893Sdfr			base += len;
2212893Sdfr			len = 0;
2222893Sdfr			continue;
2232893Sdfr		}
2242893Sdfr		/* The following is safe. spec always starts with a quote. */
2252893Sdfr		if (p[-1] == '\\')
2262893Sdfr			p--;
2272893Sdfr		if (base != p) {
2282893Sdfr			sbuf_bcat(sb, base, p - base);
2292893Sdfr			len -= p - base;
2302893Sdfr			base = p;
2312893Sdfr		}
2322893Sdfr		if (*p == '\\') {
2332893Sdfr			sbuf_putc(sb, '$');
2342893Sdfr			base += 2;
2352893Sdfr			len -= 2;
2362893Sdfr			continue;
2372893Sdfr		}
2382893Sdfr		/* Skip the '$'. */
2392893Sdfr		base++;
2402893Sdfr		len--;
2412893Sdfr		/* Handle ${X} vs $X. */
2422893Sdfr		v = base;
2432893Sdfr		if (*base == '{') {
2442893Sdfr			p = strchr(v, '}');
2452893Sdfr			if (p == NULL)
2462893Sdfr				p = v;
2472893Sdfr		} else
2482893Sdfr			p = v;
2492893Sdfr		len -= (p + 1) - base;
2502893Sdfr		base = p + 1;
2512893Sdfr
2522893Sdfr		if (v == p) {
2532893Sdfr			sbuf_putc(sb, *v);
2542893Sdfr			continue;
2552893Sdfr		}
2562893Sdfr
2572893Sdfr		error = ENOMEM;
2582893Sdfr		var = calloc(p - v, 1);
2592893Sdfr		if (var == NULL)
2602893Sdfr			break;
2612893Sdfr
2622893Sdfr		memcpy(var, v + 1, p - v - 1);
2632893Sdfr		if (strcmp(var, ".CURDIR") == 0) {
2642893Sdfr			res = getcwd(NULL, 0);
2652893Sdfr			if (res == NULL)
2662893Sdfr				break;
2672893Sdfr		} else if (strcmp(var, ".PROG") == 0) {
2682893Sdfr			res = strdup(getprogname());
2692893Sdfr			if (res == NULL)
2702893Sdfr				break;
2712893Sdfr		} else {
2722893Sdfr			v = getenv(var);
2732893Sdfr			if (v != NULL) {
2742893Sdfr				res = strdup(v);
2752893Sdfr				if (res == NULL)
2762893Sdfr					break;
2772893Sdfr			} else
2782893Sdfr				res = NULL;
2792893Sdfr		}
2802893Sdfr		error = 0;
2812893Sdfr
2822893Sdfr		if (res != NULL) {
2832893Sdfr			sbuf_cat(sb, res);
2842893Sdfr			free(res);
2852893Sdfr		}
2862893Sdfr		free(var);
2872893Sdfr	}
2882893Sdfr
2892893Sdfr	sbuf_finish(sb);
2902893Sdfr	res = (error == 0) ? strdup(sbuf_data(sb)) : NULL;
2912893Sdfr	sbuf_delete(sb);
2922893Sdfr	if (res == NULL)
2932893Sdfr		errno = ENOMEM;
2942893Sdfr	return (res);
2952893Sdfr}
2962893Sdfr
2972893Sdfrstatic int
2982893Sdfrskip_over(FILE *fp, const char *cs)
2992893Sdfr{
3002893Sdfr	int c;
3012893Sdfr
3022893Sdfr	c = getc(fp);
3032893Sdfr	while (c != EOF && strchr(cs, c) != NULL)
3042893Sdfr		c = getc(fp);
3052893Sdfr	if (c != EOF) {
3062893Sdfr		ungetc(c, fp);
3072893Sdfr		return (0);
3082893Sdfr	}
3092893Sdfr	return (ferror(fp) ? errno : -1);
3102893Sdfr}
3112893Sdfr
3122893Sdfrstatic int
3132893Sdfrskip_to(FILE *fp, const char *cs)
3142893Sdfr{
3152893Sdfr	int c;
3162893Sdfr
3172893Sdfr	c = getc(fp);
3182893Sdfr	while (c != EOF && strchr(cs, c) == NULL)
3192893Sdfr		c = getc(fp);
3202893Sdfr	if (c != EOF) {
3212893Sdfr		ungetc(c, fp);
3222893Sdfr		return (0);
3232893Sdfr	}
3242893Sdfr	return (ferror(fp) ? errno : -1);
3252893Sdfr}
3262893Sdfr
3272893Sdfrstatic int
3282893Sdfrread_word(FILE *fp, char *buf, size_t bufsz)
3292893Sdfr{
3302893Sdfr	struct mtree_fileinfo *fi;
3312893Sdfr	size_t idx, qidx;
3322893Sdfr	int c, done, error, esc, qlvl;
3332893Sdfr
3342893Sdfr	if (bufsz == 0)
3352893Sdfr		return (EINVAL);
3362893Sdfr
3372893Sdfr	done = 0;
3382893Sdfr	esc = 0;
3392893Sdfr	idx = 0;
3402893Sdfr	qidx = -1;
3412893Sdfr	qlvl = 0;
3422893Sdfr	do {
3432893Sdfr		c = getc(fp);
3442893Sdfr		switch (c) {
3452893Sdfr		case EOF:
3462893Sdfr			buf[idx] = '\0';
3472893Sdfr			error = ferror(fp) ? errno : -1;
3482893Sdfr			if (error == -1)
3492893Sdfr				mtree_error("unexpected end of file");
3502893Sdfr			return (error);
3512893Sdfr		case '\\':
3522893Sdfr			esc++;
3532893Sdfr			if (esc == 1)
3542893Sdfr				continue;
3552893Sdfr			break;
3562893Sdfr		case '`':
3572893Sdfr		case '\'':
3582893Sdfr		case '"':
3592893Sdfr			if (esc)
3602893Sdfr				break;
3612893Sdfr			if (qlvl == 0) {
3622893Sdfr				qlvl++;
3632893Sdfr				qidx = idx;
3642893Sdfr			} else if (c == buf[qidx]) {
3652893Sdfr				qlvl--;
3662893Sdfr				if (qlvl > 0) {
3672893Sdfr					do {
3682893Sdfr						qidx--;
3692893Sdfr					} while (buf[qidx] != '`' &&
3702893Sdfr					    buf[qidx] != '\'' &&
3712893Sdfr					    buf[qidx] != '"');
3722893Sdfr				} else
3732893Sdfr					qidx = -1;
3742893Sdfr			} else {
3752893Sdfr				qlvl++;
3762893Sdfr				qidx = idx;
3772893Sdfr			}
3782893Sdfr			break;
3792893Sdfr		case ' ':
3802893Sdfr		case '\t':
3812893Sdfr		case '\n':
3822893Sdfr			if (!esc && qlvl == 0) {
3832893Sdfr				ungetc(c, fp);
3842893Sdfr				c = '\0';
3852893Sdfr				done = 1;
3862893Sdfr				break;
3872893Sdfr			}
3882893Sdfr			if (c == '\n') {
3892893Sdfr				/*
3902893Sdfr				 * We going to eat the newline ourselves.
3912893Sdfr				 */
3922893Sdfr				if (qlvl > 0)
3932893Sdfr					mtree_warning("quoted word straddles "
3942893Sdfr					    "onto next line.");
3952893Sdfr				fi = SLIST_FIRST(&mtree_fileinfo);
3962893Sdfr				fi->line++;
3972893Sdfr			}
3982893Sdfr			break;
3992893Sdfr		case 'a':
4002893Sdfr			if (esc)
4012893Sdfr				c = '\a';
4022893Sdfr			break;
4032893Sdfr		case 'b':
4042893Sdfr			if (esc)
4052893Sdfr				c = '\b';
4062893Sdfr			break;
4072893Sdfr		case 'f':
4082893Sdfr			if (esc)
4092893Sdfr				c = '\f';
4102893Sdfr			break;
4112893Sdfr		case 'n':
4122893Sdfr			if (esc)
4132893Sdfr				c = '\n';
4142893Sdfr			break;
4152893Sdfr		case 'r':
4162893Sdfr			if (esc)
4172893Sdfr				c = '\r';
4182893Sdfr			break;
4192893Sdfr		case 't':
4202893Sdfr			if (esc)
4212893Sdfr				c = '\t';
4222893Sdfr			break;
4232893Sdfr		case 'v':
4242893Sdfr			if (esc)
4252893Sdfr				c = '\v';
4262893Sdfr			break;
4272893Sdfr		}
4282893Sdfr		buf[idx++] = c;
4292893Sdfr		esc = 0;
4302893Sdfr	} while (idx < bufsz && !done);
4312893Sdfr
4322893Sdfr	if (idx >= bufsz) {
4332893Sdfr		mtree_error("word too long to fit buffer (max %zu characters)",
4342893Sdfr		    bufsz);
4352893Sdfr		skip_to(fp, " \t\n");
4362893Sdfr	}
4372893Sdfr	return (0);
4382893Sdfr}
4392893Sdfr
4402893Sdfrstatic fsnode *
4412893Sdfrcreate_node(const char *name, u_int type, fsnode *parent, fsnode *global)
4422893Sdfr{
4432893Sdfr	fsnode *n;
4442893Sdfr
4452893Sdfr	n = calloc(1, sizeof(*n));
4462893Sdfr	if (n == NULL)
4472893Sdfr		return (NULL);
4482893Sdfr
4492893Sdfr	n->name = strdup(name);
4502893Sdfr	if (n->name == NULL) {
4512893Sdfr		free(n);
4522893Sdfr		return (NULL);
4532893Sdfr	}
4542893Sdfr
4552893Sdfr	n->type = (type == 0) ? global->type : type;
4562893Sdfr	n->parent = parent;
4572893Sdfr
4582893Sdfr	n->inode = calloc(1, sizeof(*n->inode));
4592893Sdfr	if (n->inode == NULL) {
4602893Sdfr		free(n->name);
4612893Sdfr		free(n);
4622893Sdfr		return (NULL);
4632893Sdfr	}
4642893Sdfr
4652893Sdfr	/* Assign global options/defaults. */
4662893Sdfr	bcopy(global->inode, n->inode, sizeof(*n->inode));
4672893Sdfr	n->inode->st.st_mode = (n->inode->st.st_mode & ~S_IFMT) | n->type;
4682893Sdfr
4692893Sdfr	if (n->type == S_IFLNK)
4702893Sdfr		n->symlink = global->symlink;
4712893Sdfr	else if (n->type == S_IFREG)
4722893Sdfr		n->contents = global->contents;
4732893Sdfr
4742893Sdfr	return (n);
4752893Sdfr}
4762893Sdfr
4772893Sdfrstatic void
4782893Sdfrdestroy_node(fsnode *n)
4792893Sdfr{
4802893Sdfr
4812893Sdfr	assert(n != NULL);
4822893Sdfr	assert(n->name != NULL);
4832893Sdfr	assert(n->inode != NULL);
4842893Sdfr
4852893Sdfr	free(n->inode);
4862893Sdfr	free(n->name);
4872893Sdfr	free(n);
4882893Sdfr}
4892893Sdfr
4902893Sdfrstatic int
4912893Sdfrread_number(const char *tok, u_int base, intmax_t *res, intmax_t min,
4922893Sdfr    intmax_t max)
4932893Sdfr{
4942893Sdfr	char *end;
4952893Sdfr	intmax_t val;
4962893Sdfr
4972893Sdfr	val = strtoimax(tok, &end, base);
4982893Sdfr	if (end == tok || end[0] != '\0')
4992893Sdfr		return (EINVAL);
5002893Sdfr	if (val < min || val > max)
5012893Sdfr		return (EDOM);
5022893Sdfr	*res = val;
5032893Sdfr	return (0);
5042893Sdfr}
5052893Sdfr
5062893Sdfrstatic int
5072893Sdfrread_mtree_keywords(FILE *fp, fsnode *node)
5082893Sdfr{
5092893Sdfr	char keyword[PATH_MAX];
5102893Sdfr	char *name, *p, *value;
5112893Sdfr	gid_t gid;
5122893Sdfr	uid_t uid;
5132893Sdfr	struct stat *st, sb;
5142893Sdfr	intmax_t num;
5152893Sdfr	u_long flset, flclr;
5162893Sdfr	int error, istemp, type;
5172893Sdfr
5182893Sdfr	st = &node->inode->st;
5192893Sdfr	do {
5202893Sdfr		error = skip_over(fp, " \t");
5212893Sdfr		if (error)
5222893Sdfr			break;
5232893Sdfr
5242893Sdfr		error = read_word(fp, keyword, sizeof(keyword));
5252893Sdfr		if (error)
5262893Sdfr			break;
5272893Sdfr
5282893Sdfr		if (keyword[0] == '\0')
5292893Sdfr			break;
5302893Sdfr
5312893Sdfr		value = strchr(keyword, '=');
5322893Sdfr		if (value != NULL)
5332893Sdfr			*value++ = '\0';
5342893Sdfr
5352893Sdfr		/*
5362893Sdfr		 * We use EINVAL, ENOATTR, ENOSYS and ENXIO to signal
5372893Sdfr		 * certain conditions:
5382893Sdfr		 *   EINVAL -	Value provided for a keyword that does
5392893Sdfr		 *		not take a value. The value is ignored.
5402893Sdfr		 *   ENOATTR -	Value missing for a keyword that needs
5412893Sdfr		 *		a value. The keyword is ignored.
5422893Sdfr		 *   ENOSYS -	Unsupported keyword encountered. The
5432893Sdfr		 *		keyword is ignored.
5442893Sdfr		 *   ENXIO -	Value provided for a keyword that does
5452893Sdfr		 *		not take a value. The value is ignored.
5462893Sdfr		 */
5472893Sdfr		switch (keyword[0]) {
5482893Sdfr		case 'c':
5492893Sdfr			if (strcmp(keyword, "contents") == 0) {
5502893Sdfr				if (value == NULL) {
5512893Sdfr					error = ENOATTR;
5522893Sdfr					break;
5532893Sdfr				}
5542893Sdfr				node->contents = strdup(value);
5552893Sdfr			} else
5562893Sdfr				error = ENOSYS;
5572893Sdfr			break;
5582893Sdfr		case 'f':
5592893Sdfr			if (strcmp(keyword, "flags") == 0) {
5602893Sdfr				if (value == NULL) {
5612893Sdfr					error = ENOATTR;
5622893Sdfr					break;
5632893Sdfr				}
5642893Sdfr				flset = flclr = 0;
5652893Sdfr				if (!strtofflags(&value, &flset, &flclr)) {
5662893Sdfr					st->st_flags &= ~flclr;
5672893Sdfr					st->st_flags |= flset;
5682893Sdfr				} else
5692893Sdfr					error = errno;
5702893Sdfr			} else
5712893Sdfr				error = ENOSYS;
5722893Sdfr			break;
5732893Sdfr		case 'g':
5742893Sdfr			if (strcmp(keyword, "gid") == 0) {
5752893Sdfr				if (value == NULL) {
5762893Sdfr					error = ENOATTR;
5772893Sdfr					break;
5782893Sdfr				}
5792893Sdfr				error = read_number(value, 10, &num,
5802893Sdfr				    0, UINT_MAX);
5812893Sdfr				if (!error)
5822893Sdfr					st->st_gid = num;
5832893Sdfr			} else if (strcmp(keyword, "gname") == 0) {
5842893Sdfr				if (value == NULL) {
5852893Sdfr					error = ENOATTR;
5862893Sdfr					break;
5872893Sdfr				}
5882893Sdfr				if (gid_from_group(value, &gid) == 0)
5892893Sdfr					st->st_gid = gid;
5902893Sdfr				else
5912893Sdfr					error = EINVAL;
5922893Sdfr			} else
5932893Sdfr				error = ENOSYS;
5942893Sdfr			break;
5952893Sdfr		case 'l':
5962893Sdfr			if (strcmp(keyword, "link") == 0) {
5972893Sdfr				if (value == NULL) {
5982893Sdfr					error = ENOATTR;
5992893Sdfr					break;
6002893Sdfr				}
6012893Sdfr				node->symlink = strdup(value);
6022893Sdfr			} else
6032893Sdfr				error = ENOSYS;
6042893Sdfr			break;
6052893Sdfr		case 'm':
6062893Sdfr			if (strcmp(keyword, "mode") == 0) {
6072893Sdfr				if (value == NULL) {
6082893Sdfr					error = ENOATTR;
6092893Sdfr					break;
6102893Sdfr				}
6112893Sdfr				if (value[0] >= '0' && value[0] <= '9') {
6122893Sdfr					error = read_number(value, 8, &num,
6132893Sdfr					    0, 07777);
6142893Sdfr					if (!error) {
6152893Sdfr						st->st_mode &= S_IFMT;
6162893Sdfr						st->st_mode |= num;
6172893Sdfr					}
6182893Sdfr				} else {
6192893Sdfr					/* Symbolic mode not supported. */
6202893Sdfr					error = EINVAL;
6212893Sdfr					break;
6222893Sdfr				}
6232893Sdfr			} else
6242893Sdfr				error = ENOSYS;
6252893Sdfr			break;
6262893Sdfr		case 'o':
6272893Sdfr			if (strcmp(keyword, "optional") == 0) {
6282893Sdfr				if (value != NULL)
6292893Sdfr					error = ENXIO;
6302893Sdfr				node->flags |= FSNODE_F_OPTIONAL;
6312893Sdfr			} else
6322893Sdfr				error = ENOSYS;
6332893Sdfr			break;
6342893Sdfr		case 's':
6352893Sdfr			if (strcmp(keyword, "size") == 0) {
6362893Sdfr				if (value == NULL) {
6372893Sdfr					error = ENOATTR;
6382893Sdfr					break;
6392893Sdfr				}
6402893Sdfr				error = read_number(value, 10, &num,
6412893Sdfr				    0, INTMAX_MAX);
6422893Sdfr				if (!error)
6432893Sdfr					st->st_size = num;
6442893Sdfr			} else
6452893Sdfr				error = ENOSYS;
6462893Sdfr			break;
6472893Sdfr		case 't':
6482893Sdfr			if (strcmp(keyword, "time") == 0) {
6492893Sdfr				if (value == NULL) {
6502893Sdfr					error = ENOATTR;
6512893Sdfr					break;
6522893Sdfr				}
6532893Sdfr				p = strchr(value, '.');
6542893Sdfr				if (p != NULL)
6552893Sdfr					*p++ = '\0';
6562893Sdfr				error = read_number(value, 10, &num, 0,
6572893Sdfr				    INTMAX_MAX);
6582893Sdfr				if (error)
6592893Sdfr					break;
6602893Sdfr				st->st_atime = num;
6612893Sdfr				st->st_ctime = num;
6622893Sdfr				st->st_mtime = num;
6632893Sdfr				error = read_number(p, 10, &num, 0,
6642893Sdfr				    INTMAX_MAX);
6652893Sdfr				if (error)
6662893Sdfr					break;
6672893Sdfr				if (num != 0)
6682893Sdfr					error = EINVAL;
6692893Sdfr			} else if (strcmp(keyword, "type") == 0) {
6702893Sdfr				if (value == NULL) {
6712893Sdfr					error = ENOATTR;
6722893Sdfr					break;
6732893Sdfr				}
6742893Sdfr				if (strcmp(value, "dir") == 0)
6752893Sdfr					node->type = S_IFDIR;
6762893Sdfr				else if (strcmp(value, "file") == 0)
6772893Sdfr					node->type = S_IFREG;
6782893Sdfr				else if (strcmp(value, "link") == 0)
6792893Sdfr					node->type = S_IFLNK;
6802893Sdfr				else
6812893Sdfr					error = EINVAL;
6822893Sdfr			} else
6832893Sdfr				error = ENOSYS;
6842893Sdfr			break;
6852893Sdfr		case 'u':
6862893Sdfr			if (strcmp(keyword, "uid") == 0) {
6872893Sdfr				if (value == NULL) {
6882893Sdfr					error = ENOATTR;
6892893Sdfr					break;
6902893Sdfr				}
6912893Sdfr				error = read_number(value, 10, &num,
6922893Sdfr				    0, UINT_MAX);
6932893Sdfr				if (!error)
6942893Sdfr					st->st_uid = num;
6952893Sdfr			} else if (strcmp(keyword, "uname") == 0) {
6962893Sdfr				if (value == NULL) {
6972893Sdfr					error = ENOATTR;
6982893Sdfr					break;
6992893Sdfr				}
7002893Sdfr				if (uid_from_user(value, &uid) == 0)
7012893Sdfr					st->st_uid = uid;
7022893Sdfr				else
7032893Sdfr					error = EINVAL;
7042893Sdfr			} else
7052893Sdfr				error = ENOSYS;
7062893Sdfr			break;
7072893Sdfr		default:
7082893Sdfr			error = ENOSYS;
7092893Sdfr			break;
7102893Sdfr		}
7112893Sdfr
7122893Sdfr		switch (error) {
7132893Sdfr		case EINVAL:
7142893Sdfr			mtree_error("%s: invalid value '%s'", keyword, value);
7152893Sdfr			break;
7162893Sdfr		case ENOATTR:
7172893Sdfr			mtree_error("%s: keyword needs a value", keyword);
7182893Sdfr			break;
7192893Sdfr		case ENOSYS:
7202893Sdfr			mtree_warning("%s: unsupported keyword", keyword);
7212893Sdfr			break;
7222893Sdfr		case ENXIO:
7232893Sdfr			mtree_error("%s: keyword does not take a value",
7242893Sdfr			    keyword);
7252893Sdfr			break;
7262893Sdfr		}
7272893Sdfr	} while (1);
7282893Sdfr
7292893Sdfr	if (error)
7302893Sdfr		return (error);
7312893Sdfr
7322893Sdfr	st->st_mode = (st->st_mode & ~S_IFMT) | node->type;
7332893Sdfr
734	/* Nothing more to do for the global defaults. */
735	if (node->name == NULL)
736		return (0);
737
738	/*
739	 * Be intelligent about the file type.
740	 */
741	if (node->contents != NULL) {
742		if (node->symlink != NULL) {
743			mtree_error("%s: both link and contents keywords "
744			    "defined", node->name);
745			return (0);
746		}
747		type = S_IFREG;
748	} else if (node->type != 0) {
749		type = node->type;
750		if (type == S_IFREG) {
751			/* the named path is the default contents */
752			node->contents = mtree_file_path(node);
753		}
754	} else
755		type = (node->symlink != NULL) ? S_IFLNK : S_IFDIR;
756
757	if (node->type == 0)
758		node->type = type;
759
760	if (node->type != type) {
761		mtree_error("%s: file type and defined keywords to not match",
762		    node->name);
763		return (0);
764	}
765
766	st->st_mode = (st->st_mode & ~S_IFMT) | node->type;
767
768	if (node->contents == NULL)
769		return (0);
770
771	name = mtree_resolve(node->contents, &istemp);
772	if (name == NULL)
773		return (errno);
774
775	if (stat(name, &sb) != 0) {
776		mtree_error("%s: contents file '%s' not found", node->name,
777		    name);
778		free(name);
779		return (0);
780	}
781
782	free(node->contents);
783	node->contents = name;
784	st->st_size = sb.st_size;
785	return (0);
786}
787
788static int
789read_mtree_command(FILE *fp)
790{
791	char cmd[10];
792	int error;
793
794	error = read_word(fp, cmd, sizeof(cmd));
795	if (error)
796		goto out;
797
798	error = read_mtree_keywords(fp, &mtree_global);
799
800 out:
801	skip_to(fp, "\n");
802	(void)getc(fp);
803	return (error);
804}
805
806static int
807read_mtree_spec1(FILE *fp, bool def, const char *name)
808{
809	fsnode *last, *node, *parent;
810	u_int type;
811	int error;
812
813	assert(name[0] != '\0');
814
815	/*
816	 * Treat '..' specially, because it only changes our current
817	 * directory. We don't create a node for it. We simply ignore
818	 * any keywords that may appear on the line as well.
819	 * Going up a directory is a little non-obvious. A directory
820	 * node has a corresponding '.' child. The parent of '.' is
821	 * not the '.' node of the parent directory, but the directory
822	 * node within the parent to which the child relates. However,
823	 * going up a directory means we need to find the '.' node to
824	 * which the directoy node is linked.  This we can do via the
825	 * first * pointer, because '.' is always the first entry in a
826	 * directory.
827	 */
828	if (IS_DOTDOT(name)) {
829		/* This deals with NULL pointers as well. */
830		if (mtree_current == mtree_root) {
831			mtree_warning("ignoring .. in root directory");
832			return (0);
833		}
834
835		node = mtree_current;
836
837		assert(node != NULL);
838		assert(IS_DOT(node->name));
839		assert(node->first == node);
840
841		/* Get the corresponding directory node in the parent. */
842		node = mtree_current->parent;
843
844		assert(node != NULL);
845		assert(!IS_DOT(node->name));
846
847		node = node->first;
848
849		assert(node != NULL);
850		assert(IS_DOT(node->name));
851		assert(node->first == node);
852
853		mtree_current = node;
854		return (0);
855	}
856
857	/*
858	 * If we don't have a current directory and the first specification
859	 * (either implicit or defined) is not '.', then we need to create
860	 * a '.' node first (using a recursive call).
861	 */
862	if (!IS_DOT(name) && mtree_current == NULL) {
863		error = read_mtree_spec1(fp, false, ".");
864		if (error)
865			return (error);
866	}
867
868	/*
869	 * Lookup the name in the current directory (if we have a current
870	 * directory) to make sure we do not create multiple nodes for the
871	 * same component. For non-definitions, if we find a node with the
872	 * same name, simply change the current directory. For definitions
873	 * more happens.
874	 */
875	last = NULL;
876	node = mtree_current;
877	while (node != NULL) {
878		assert(node->first == mtree_current);
879
880		if (strcmp(name, node->name) == 0) {
881			if (def == true) {
882				if (!dupsok)
883					mtree_error(
884					    "duplicate definition of %s",
885					    name);
886				else
887					mtree_warning(
888					    "duplicate definition of %s",
889					    name);
890				return (0);
891			}
892
893			if (node->type != S_IFDIR) {
894				mtree_error("%s is not a directory", name);
895				return (0);
896			}
897
898			assert(!IS_DOT(name));
899
900			node = node->child;
901
902			assert(node != NULL);
903			assert(IS_DOT(node->name));
904
905			mtree_current = node;
906			return (0);
907		}
908
909		last = node;
910		node = last->next;
911	}
912
913	parent = (mtree_current != NULL) ? mtree_current->parent : NULL;
914	type = (def == false || IS_DOT(name)) ? S_IFDIR : 0;
915	node = create_node(name, type, parent, &mtree_global);
916	if (node == NULL)
917		return (ENOMEM);
918
919	if (def == true) {
920		error = read_mtree_keywords(fp, node);
921		if (error) {
922			destroy_node(node);
923			return (error);
924		}
925	}
926
927	node->first = (mtree_current != NULL) ? mtree_current : node;
928
929	if (last != NULL)
930		last->next = node;
931
932	if (node->type != S_IFDIR)
933		return (0);
934
935	if (!IS_DOT(node->name)) {
936		parent = node;
937		node = create_node(".", S_IFDIR, parent, parent);
938		if (node == NULL) {
939			last->next = NULL;
940			destroy_node(parent);
941			return (ENOMEM);
942		}
943		parent->child = node;
944		node->first = node;
945	}
946
947	assert(node != NULL);
948	assert(IS_DOT(node->name));
949	assert(node->first == node);
950
951	mtree_current = node;
952	if (mtree_root == NULL)
953		mtree_root = node;
954
955	return (0);
956}
957
958static int
959read_mtree_spec(FILE *fp)
960{
961	char pathspec[PATH_MAX];
962	char *cp;
963	int error;
964
965	error = read_word(fp, pathspec, sizeof(pathspec));
966	if (error)
967		goto out;
968
969	cp = strchr(pathspec, '/');
970	if (cp != NULL) {
971		/* Absolute pathname */
972		mtree_current = mtree_root;
973
974		do {
975			*cp++ = '\0';
976
977			/* Disallow '..' as a component. */
978			if (IS_DOTDOT(pathspec)) {
979				mtree_error("absolute path cannot contain "
980				    ".. component");
981				goto out;
982			}
983
984			/* Ignore multiple adjacent slashes and '.'. */
985			if (pathspec[0] != '\0' && !IS_DOT(pathspec))
986				error = read_mtree_spec1(fp, false, pathspec);
987			memmove(pathspec, cp, strlen(cp) + 1);
988			cp = strchr(pathspec, '/');
989		} while (!error && cp != NULL);
990
991		/* Disallow '.' and '..' as the last component. */
992		if (!error && (IS_DOT(pathspec) || IS_DOTDOT(pathspec))) {
993			mtree_error("absolute path cannot contain . or .. "
994			    "components");
995			goto out;
996		}
997	}
998
999	/* Ignore absolute specfications that end with a slash. */
1000	if (!error && pathspec[0] != '\0')
1001		error = read_mtree_spec1(fp, true, pathspec);
1002
1003 out:
1004	skip_to(fp, "\n");
1005	(void)getc(fp);
1006	return (error);
1007}
1008
1009fsnode *
1010read_mtree(const char *fname, fsnode *node)
1011{
1012	struct mtree_fileinfo *fi;
1013	FILE *fp;
1014	int c, error;
1015
1016	/* We do not yet support nesting... */
1017	assert(node == NULL);
1018
1019	if (strcmp(fname, "-") == 0)
1020		fp = stdin;
1021	else {
1022		fp = fopen(fname, "r");
1023		if (fp == NULL)
1024			err(1, "Can't open `%s'", fname);
1025	}
1026
1027	error = mtree_file_push(fname, fp);
1028	if (error)
1029		goto out;
1030
1031	bzero(&mtree_global, sizeof(mtree_global));
1032	bzero(&mtree_global_inode, sizeof(mtree_global_inode));
1033	mtree_global.inode = &mtree_global_inode;
1034	mtree_global_inode.nlink = 1;
1035	mtree_global_inode.st.st_atime = mtree_global_inode.st.st_ctime =
1036	    mtree_global_inode.st.st_mtime = time(NULL);
1037	errors = warnings = 0;
1038
1039	setgroupent(1);
1040	setpassent(1);
1041
1042	mtree_root = node;
1043	mtree_current = node;
1044	do {
1045		/* Start of a new line... */
1046		fi = SLIST_FIRST(&mtree_fileinfo);
1047		fi->line++;
1048
1049		error = skip_over(fp, " \t");
1050		if (error)
1051			break;
1052
1053		c = getc(fp);
1054		if (c == EOF) {
1055			error = ferror(fp) ? errno : -1;
1056			break;
1057		}
1058
1059		switch (c) {
1060		case '\n':		/* empty line */
1061			error = 0;
1062			break;
1063		case '#':		/* comment -- skip to end of line. */
1064			error = skip_to(fp, "\n");
1065			if (!error)
1066				(void)getc(fp);
1067			break;
1068		case '/':		/* special commands */
1069			error = read_mtree_command(fp);
1070			break;
1071		default:		/* specification */
1072			ungetc(c, fp);
1073			error = read_mtree_spec(fp);
1074			break;
1075		}
1076	} while (!error);
1077
1078	endpwent();
1079	endgrent();
1080
1081	if (error <= 0 && (errors || warnings)) {
1082		warnx("%u error(s) and %u warning(s) in mtree manifest",
1083		    errors, warnings);
1084		if (errors)
1085			exit(1);
1086	}
1087
1088 out:
1089	if (error > 0)
1090		errc(1, error, "Error reading mtree file");
1091
1092	if (fp != stdin)
1093		fclose(fp);
1094
1095	if (mtree_root != NULL)
1096		return (mtree_root);
1097
1098	/* Handle empty specifications. */
1099	node = create_node(".", S_IFDIR, NULL, &mtree_global);
1100	node->first = node;
1101	return (node);
1102}
1103