mtree.c revision 242501
1223306Smarcel/*-
2223306Smarcel * Copyright (c) 2011 Marcel Moolenaar
3223306Smarcel * All rights reserved.
4223306Smarcel *
5223306Smarcel * Redistribution and use in source and binary forms, with or without
6223306Smarcel * modification, are permitted provided that the following conditions
7223306Smarcel * are met:
8223306Smarcel * 1. Redistributions of source code must retain the above copyright
9223306Smarcel *    notice, this list of conditions and the following disclaimer.
10223306Smarcel * 2. Redistributions in binary form must reproduce the above copyright
11223306Smarcel *    notice, this list of conditions and the following disclaimer in the
12223306Smarcel *    documentation and/or other materials provided with the distribution.
13223306Smarcel *
14223306Smarcel * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
15223306Smarcel * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16223306Smarcel * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17223306Smarcel * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18223306Smarcel * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19223306Smarcel * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20223306Smarcel * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21223306Smarcel * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22223306Smarcel * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23223306Smarcel * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24223306Smarcel */
25223306Smarcel
26223306Smarcel#include <sys/cdefs.h>
27223306Smarcel__FBSDID("$FreeBSD: head/usr.sbin/makefs/mtree.c 242501 2012-11-03 00:10:30Z sjg $");
28223306Smarcel
29223306Smarcel#include <sys/param.h>
30223306Smarcel#include <sys/queue.h>
31223306Smarcel#include <sys/sbuf.h>
32223306Smarcel#include <sys/stat.h>
33223306Smarcel#include <sys/types.h>
34223306Smarcel#include <assert.h>
35223306Smarcel#include <errno.h>
36223306Smarcel#include <fcntl.h>
37223306Smarcel#include <grp.h>
38223306Smarcel#include <inttypes.h>
39223306Smarcel#include <pwd.h>
40223306Smarcel#include <stdarg.h>
41223306Smarcel#include <stdbool.h>
42223306Smarcel#include <stddef.h>
43223306Smarcel#include <stdio.h>
44223306Smarcel#include <stdlib.h>
45223306Smarcel#include <string.h>
46223306Smarcel#include <strings.h>
47223306Smarcel#include <unistd.h>
48223306Smarcel
49223306Smarcel#include "makefs.h"
50223306Smarcel
51223306Smarcel#define	IS_DOT(nm)	((nm)[0] == '.' && (nm)[1] == '\0')
52223306Smarcel#define	IS_DOTDOT(nm)	((nm)[0] == '.' && (nm)[1] == '.' && (nm)[2] == '\0')
53223306Smarcel
54223306Smarcelstruct mtree_fileinfo {
55223306Smarcel	SLIST_ENTRY(mtree_fileinfo) next;
56223306Smarcel	FILE *fp;
57223306Smarcel	const char *name;
58223306Smarcel	u_int line;
59223306Smarcel};
60223306Smarcel
61223306Smarcel/* Global state used while parsing. */
62223306Smarcelstatic SLIST_HEAD(, mtree_fileinfo) mtree_fileinfo =
63223306Smarcel    SLIST_HEAD_INITIALIZER(mtree_fileinfo);
64223306Smarcelstatic fsnode *mtree_root;
65223306Smarcelstatic fsnode *mtree_current;
66223306Smarcelstatic fsnode mtree_global;
67223306Smarcelstatic fsinode mtree_global_inode;
68223306Smarcelstatic u_int errors, warnings;
69223306Smarcel
70223306Smarcelstatic void mtree_error(const char *, ...) __printflike(1, 2);
71223306Smarcelstatic void mtree_warning(const char *, ...) __printflike(1, 2);
72223306Smarcel
73223306Smarcelstatic int
74223306Smarcelmtree_file_push(const char *name, FILE *fp)
75223306Smarcel{
76223306Smarcel	struct mtree_fileinfo *fi;
77223306Smarcel
78223306Smarcel	fi = malloc(sizeof(*fi));
79223306Smarcel	if (fi == NULL)
80223306Smarcel		return (ENOMEM);
81223306Smarcel
82223306Smarcel	if (strcmp(name, "-") == 0)
83223306Smarcel		fi->name = strdup("(stdin)");
84223306Smarcel	else
85223306Smarcel		fi->name = strdup(name);
86223306Smarcel	if (fi->name == NULL) {
87223306Smarcel		free(fi);
88223306Smarcel		return (ENOMEM);
89223306Smarcel	}
90223306Smarcel
91223306Smarcel	fi->fp = fp;
92223306Smarcel	fi->line = 0;
93223306Smarcel
94223306Smarcel	SLIST_INSERT_HEAD(&mtree_fileinfo, fi, next);
95223306Smarcel	return (0);
96223306Smarcel}
97223306Smarcel
98223306Smarcelstatic void
99223306Smarcelmtree_print(const char *msgtype, const char *fmt, va_list ap)
100223306Smarcel{
101223306Smarcel	struct mtree_fileinfo *fi;
102223306Smarcel
103223306Smarcel	if (msgtype != NULL) {
104223306Smarcel		fi = SLIST_FIRST(&mtree_fileinfo);
105223306Smarcel		if (fi != NULL)
106223306Smarcel			fprintf(stderr, "%s:%u: ", fi->name, fi->line);
107223306Smarcel		fprintf(stderr, "%s: ", msgtype);
108223306Smarcel	}
109223306Smarcel	vfprintf(stderr, fmt, ap);
110223306Smarcel}
111223306Smarcel
112223306Smarcelstatic void
113223306Smarcelmtree_error(const char *fmt, ...)
114223306Smarcel{
115223306Smarcel	va_list ap;
116223306Smarcel
117223306Smarcel	va_start(ap, fmt);
118223306Smarcel	mtree_print("error", fmt, ap);
119223306Smarcel	va_end(ap);
120223306Smarcel
121223306Smarcel	errors++;
122223306Smarcel	fputc('\n', stderr);
123223306Smarcel}
124223306Smarcel
125223306Smarcelstatic void
126223306Smarcelmtree_warning(const char *fmt, ...)
127223306Smarcel{
128223306Smarcel	va_list ap;
129223306Smarcel
130223306Smarcel	va_start(ap, fmt);
131223306Smarcel	mtree_print("warning", fmt, ap);
132223306Smarcel	va_end(ap);
133223306Smarcel
134223306Smarcel	warnings++;
135223306Smarcel	fputc('\n', stderr);
136223306Smarcel}
137223306Smarcel
138242501Ssjg#ifndef MAKEFS_MAX_TREE_DEPTH
139242501Ssjg# define MAKEFS_MAX_TREE_DEPTH (MAXPATHLEN/2)
140242501Ssjg#endif
141242501Ssjg
142242501Ssjg/* construct path to node->name */
143242501Ssjgstatic char *
144242501Ssjgmtree_file_path(fsnode *node)
145242501Ssjg{
146242501Ssjg	fsnode *pnode;
147242501Ssjg	struct sbuf *sb;
148242501Ssjg	char *res, *rp[MAKEFS_MAX_TREE_DEPTH];
149242501Ssjg	int depth;
150242501Ssjg
151242501Ssjg	depth = 0;
152242501Ssjg	rp[depth] = node->name;
153242501Ssjg	for (pnode = node->parent; pnode && depth < MAKEFS_MAX_TREE_DEPTH;
154242501Ssjg	     pnode = pnode->parent) {
155242501Ssjg		if (strcmp(pnode->name, ".") == 0)
156242501Ssjg			break;
157242501Ssjg		rp[++depth] = pnode->name;
158242501Ssjg	}
159242501Ssjg
160242501Ssjg	sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND);
161242501Ssjg	if (sb == NULL) {
162242501Ssjg		errno = ENOMEM;
163242501Ssjg		return (NULL);
164242501Ssjg	}
165242501Ssjg	while (depth > 0) {
166242501Ssjg		sbuf_cat(sb, rp[depth--]);
167242501Ssjg		sbuf_putc(sb, '/');
168242501Ssjg	}
169242501Ssjg	sbuf_cat(sb, rp[depth]);
170242501Ssjg	sbuf_finish(sb);
171242501Ssjg	res = strdup(sbuf_data(sb));
172242501Ssjg	sbuf_delete(sb);
173242501Ssjg	if (res == NULL)
174242501Ssjg		errno = ENOMEM;
175242501Ssjg	return res;
176242501Ssjg
177242501Ssjg}
178242501Ssjg
179223306Smarcel/* mtree_resolve() sets errno to indicate why NULL was returned. */
180223306Smarcelstatic char *
181223306Smarcelmtree_resolve(const char *spec, int *istemp)
182223306Smarcel{
183223306Smarcel	struct sbuf *sb;
184223306Smarcel	char *res, *var;
185223306Smarcel	const char *base, *p, *v;
186223306Smarcel	size_t len;
187223306Smarcel	int c, error, quoted, subst;
188223306Smarcel
189223306Smarcel	len = strlen(spec);
190223306Smarcel	if (len == 0) {
191223306Smarcel		errno = EINVAL;
192223306Smarcel		return (NULL);
193223306Smarcel	}
194223306Smarcel
195223306Smarcel	c = (len > 1) ? (spec[0] == spec[len - 1]) ? spec[0] : 0 : 0;
196223306Smarcel	*istemp = (c == '`') ? 1 : 0;
197223306Smarcel	subst = (c == '`' || c == '"') ? 1 : 0;
198223306Smarcel	quoted = (subst || c == '\'') ? 1 : 0;
199223306Smarcel
200223306Smarcel	if (!subst) {
201223306Smarcel		res = strdup(spec + quoted);
202223306Smarcel		if (res != NULL && quoted)
203223306Smarcel			res[len - 2] = '\0';
204223306Smarcel		return (res);
205223306Smarcel	}
206223306Smarcel
207223306Smarcel	sb = sbuf_new_auto();
208223306Smarcel	if (sb == NULL) {
209223306Smarcel		errno = ENOMEM;
210223306Smarcel		return (NULL);
211223306Smarcel	}
212223306Smarcel
213223306Smarcel	base = spec + 1;
214223306Smarcel	len -= 2;
215223306Smarcel	error = 0;
216223306Smarcel	while (len > 0) {
217223306Smarcel		p = strchr(base, '$');
218223306Smarcel		if (p == NULL) {
219223306Smarcel			sbuf_bcat(sb, base, len);
220223306Smarcel			base += len;
221223306Smarcel			len = 0;
222223306Smarcel			continue;
223223306Smarcel		}
224223306Smarcel		/* The following is safe. spec always starts with a quote. */
225223306Smarcel		if (p[-1] == '\\')
226223306Smarcel			p--;
227223306Smarcel		if (base != p) {
228223306Smarcel			sbuf_bcat(sb, base, p - base);
229223306Smarcel			len -= p - base;
230223306Smarcel			base = p;
231223306Smarcel		}
232223306Smarcel		if (*p == '\\') {
233223306Smarcel			sbuf_putc(sb, '$');
234223306Smarcel			base += 2;
235223306Smarcel			len -= 2;
236223306Smarcel			continue;
237223306Smarcel		}
238223306Smarcel		/* Skip the '$'. */
239223306Smarcel		base++;
240223306Smarcel		len--;
241223306Smarcel		/* Handle ${X} vs $X. */
242223306Smarcel		v = base;
243223306Smarcel		if (*base == '{') {
244223306Smarcel			p = strchr(v, '}');
245223306Smarcel			if (p == NULL)
246223306Smarcel				p = v;
247223306Smarcel		} else
248223306Smarcel			p = v;
249223306Smarcel		len -= (p + 1) - base;
250223306Smarcel		base = p + 1;
251223306Smarcel
252223306Smarcel		if (v == p) {
253223306Smarcel			sbuf_putc(sb, *v);
254223306Smarcel			continue;
255223306Smarcel		}
256223306Smarcel
257223306Smarcel		error = ENOMEM;
258223306Smarcel		var = calloc(p - v, 1);
259223306Smarcel		if (var == NULL)
260223306Smarcel			break;
261223306Smarcel
262223306Smarcel		memcpy(var, v + 1, p - v - 1);
263223306Smarcel		if (strcmp(var, ".CURDIR") == 0) {
264223306Smarcel			res = getcwd(NULL, 0);
265223306Smarcel			if (res == NULL)
266223306Smarcel				break;
267223306Smarcel		} else if (strcmp(var, ".PROG") == 0) {
268223306Smarcel			res = strdup(getprogname());
269223306Smarcel			if (res == NULL)
270223306Smarcel				break;
271223306Smarcel		} else {
272223306Smarcel			v = getenv(var);
273223306Smarcel			if (v != NULL) {
274223306Smarcel				res = strdup(v);
275223306Smarcel				if (res == NULL)
276223306Smarcel					break;
277223306Smarcel			} else
278223306Smarcel				res = NULL;
279223306Smarcel		}
280223306Smarcel		error = 0;
281223306Smarcel
282223306Smarcel		if (res != NULL) {
283223306Smarcel			sbuf_cat(sb, res);
284223306Smarcel			free(res);
285223306Smarcel		}
286223306Smarcel		free(var);
287223306Smarcel	}
288223306Smarcel
289223306Smarcel	sbuf_finish(sb);
290223306Smarcel	res = (error == 0) ? strdup(sbuf_data(sb)) : NULL;
291223306Smarcel	sbuf_delete(sb);
292223306Smarcel	if (res == NULL)
293223306Smarcel		errno = ENOMEM;
294223306Smarcel	return (res);
295223306Smarcel}
296223306Smarcel
297223306Smarcelstatic int
298223306Smarcelskip_over(FILE *fp, const char *cs)
299223306Smarcel{
300223306Smarcel	int c;
301223306Smarcel
302223306Smarcel	c = getc(fp);
303223306Smarcel	while (c != EOF && strchr(cs, c) != NULL)
304223306Smarcel		c = getc(fp);
305223306Smarcel	if (c != EOF) {
306223306Smarcel		ungetc(c, fp);
307223306Smarcel		return (0);
308223306Smarcel	}
309223306Smarcel	return (ferror(fp) ? errno : -1);
310223306Smarcel}
311223306Smarcel
312223306Smarcelstatic int
313223306Smarcelskip_to(FILE *fp, const char *cs)
314223306Smarcel{
315223306Smarcel	int c;
316223306Smarcel
317223306Smarcel	c = getc(fp);
318223306Smarcel	while (c != EOF && strchr(cs, c) == NULL)
319223306Smarcel		c = getc(fp);
320223306Smarcel	if (c != EOF) {
321223306Smarcel		ungetc(c, fp);
322223306Smarcel		return (0);
323223306Smarcel	}
324223306Smarcel	return (ferror(fp) ? errno : -1);
325223306Smarcel}
326223306Smarcel
327223306Smarcelstatic int
328223306Smarcelread_word(FILE *fp, char *buf, size_t bufsz)
329223306Smarcel{
330223306Smarcel	struct mtree_fileinfo *fi;
331223306Smarcel	size_t idx, qidx;
332223306Smarcel	int c, done, error, esc, qlvl;
333223306Smarcel
334223306Smarcel	if (bufsz == 0)
335223306Smarcel		return (EINVAL);
336223306Smarcel
337223306Smarcel	done = 0;
338223306Smarcel	esc = 0;
339223306Smarcel	idx = 0;
340223306Smarcel	qidx = -1;
341223306Smarcel	qlvl = 0;
342223306Smarcel	do {
343223306Smarcel		c = getc(fp);
344223306Smarcel		switch (c) {
345223306Smarcel		case EOF:
346223306Smarcel			buf[idx] = '\0';
347223306Smarcel			error = ferror(fp) ? errno : -1;
348223306Smarcel			if (error == -1)
349223306Smarcel				mtree_error("unexpected end of file");
350223306Smarcel			return (error);
351223306Smarcel		case '\\':
352223306Smarcel			esc++;
353223306Smarcel			if (esc == 1)
354223306Smarcel				continue;
355223306Smarcel			break;
356223306Smarcel		case '`':
357223306Smarcel		case '\'':
358223306Smarcel		case '"':
359223306Smarcel			if (esc)
360223306Smarcel				break;
361223306Smarcel			if (qlvl == 0) {
362223306Smarcel				qlvl++;
363223306Smarcel				qidx = idx;
364223306Smarcel			} else if (c == buf[qidx]) {
365223306Smarcel				qlvl--;
366223306Smarcel				if (qlvl > 0) {
367223306Smarcel					do {
368223306Smarcel						qidx--;
369223306Smarcel					} while (buf[qidx] != '`' &&
370223306Smarcel					    buf[qidx] != '\'' &&
371223306Smarcel					    buf[qidx] != '"');
372223306Smarcel				} else
373223306Smarcel					qidx = -1;
374223306Smarcel			} else {
375223306Smarcel				qlvl++;
376223306Smarcel				qidx = idx;
377223306Smarcel			}
378223306Smarcel			break;
379223306Smarcel		case ' ':
380223306Smarcel		case '\t':
381223306Smarcel		case '\n':
382223306Smarcel			if (!esc && qlvl == 0) {
383223306Smarcel				ungetc(c, fp);
384223306Smarcel				c = '\0';
385223306Smarcel				done = 1;
386223306Smarcel				break;
387223306Smarcel			}
388223306Smarcel			if (c == '\n') {
389223306Smarcel				/*
390223306Smarcel				 * We going to eat the newline ourselves.
391223306Smarcel				 */
392223306Smarcel				if (qlvl > 0)
393223306Smarcel					mtree_warning("quoted word straddles "
394223306Smarcel					    "onto next line.");
395223306Smarcel				fi = SLIST_FIRST(&mtree_fileinfo);
396223306Smarcel				fi->line++;
397223306Smarcel			}
398223306Smarcel			break;
399223306Smarcel		case 'a':
400223306Smarcel			if (esc)
401223306Smarcel				c = '\a';
402223306Smarcel			break;
403223306Smarcel		case 'b':
404223306Smarcel			if (esc)
405223306Smarcel				c = '\b';
406223306Smarcel			break;
407223306Smarcel		case 'f':
408223306Smarcel			if (esc)
409223306Smarcel				c = '\f';
410223306Smarcel			break;
411223306Smarcel		case 'n':
412223306Smarcel			if (esc)
413223306Smarcel				c = '\n';
414223306Smarcel			break;
415223306Smarcel		case 'r':
416223306Smarcel			if (esc)
417223306Smarcel				c = '\r';
418223306Smarcel			break;
419223306Smarcel		case 't':
420223306Smarcel			if (esc)
421223306Smarcel				c = '\t';
422223306Smarcel			break;
423223306Smarcel		case 'v':
424223306Smarcel			if (esc)
425223306Smarcel				c = '\v';
426223306Smarcel			break;
427223306Smarcel		}
428223306Smarcel		buf[idx++] = c;
429223306Smarcel		esc = 0;
430223306Smarcel	} while (idx < bufsz && !done);
431223306Smarcel
432223306Smarcel	if (idx >= bufsz) {
433223306Smarcel		mtree_error("word too long to fit buffer (max %zu characters)",
434223306Smarcel		    bufsz);
435223306Smarcel		skip_to(fp, " \t\n");
436223306Smarcel	}
437223306Smarcel	return (0);
438223306Smarcel}
439223306Smarcel
440223306Smarcelstatic fsnode *
441223306Smarcelcreate_node(const char *name, u_int type, fsnode *parent, fsnode *global)
442223306Smarcel{
443223306Smarcel	fsnode *n;
444223306Smarcel
445223306Smarcel	n = calloc(1, sizeof(*n));
446223306Smarcel	if (n == NULL)
447223306Smarcel		return (NULL);
448223306Smarcel
449223306Smarcel	n->name = strdup(name);
450223306Smarcel	if (n->name == NULL) {
451223306Smarcel		free(n);
452223306Smarcel		return (NULL);
453223306Smarcel	}
454223306Smarcel
455223306Smarcel	n->type = (type == 0) ? global->type : type;
456223306Smarcel	n->parent = parent;
457223306Smarcel
458223306Smarcel	n->inode = calloc(1, sizeof(*n->inode));
459223306Smarcel	if (n->inode == NULL) {
460223306Smarcel		free(n->name);
461223306Smarcel		free(n);
462223306Smarcel		return (NULL);
463223306Smarcel	}
464223306Smarcel
465223306Smarcel	/* Assign global options/defaults. */
466223306Smarcel	bcopy(global->inode, n->inode, sizeof(*n->inode));
467223306Smarcel	n->inode->st.st_mode = (n->inode->st.st_mode & ~S_IFMT) | n->type;
468223306Smarcel
469223306Smarcel	if (n->type == S_IFLNK)
470223306Smarcel		n->symlink = global->symlink;
471223306Smarcel	else if (n->type == S_IFREG)
472223306Smarcel		n->contents = global->contents;
473223306Smarcel
474223306Smarcel	return (n);
475223306Smarcel}
476223306Smarcel
477223306Smarcelstatic void
478223306Smarceldestroy_node(fsnode *n)
479223306Smarcel{
480223306Smarcel
481223306Smarcel	assert(n != NULL);
482223306Smarcel	assert(n->name != NULL);
483223306Smarcel	assert(n->inode != NULL);
484223306Smarcel
485223306Smarcel	free(n->inode);
486223306Smarcel	free(n->name);
487223306Smarcel	free(n);
488223306Smarcel}
489223306Smarcel
490223306Smarcelstatic int
491223306Smarcelread_number(const char *tok, u_int base, intmax_t *res, intmax_t min,
492223306Smarcel    intmax_t max)
493223306Smarcel{
494223306Smarcel	char *end;
495223306Smarcel	intmax_t val;
496223306Smarcel
497223306Smarcel	val = strtoimax(tok, &end, base);
498223306Smarcel	if (end == tok || end[0] != '\0')
499223306Smarcel		return (EINVAL);
500223306Smarcel	if (val < min || val > max)
501223306Smarcel		return (EDOM);
502223306Smarcel	*res = val;
503223306Smarcel	return (0);
504223306Smarcel}
505223306Smarcel
506223306Smarcelstatic int
507223306Smarcelread_mtree_keywords(FILE *fp, fsnode *node)
508223306Smarcel{
509223306Smarcel	char keyword[PATH_MAX];
510223306Smarcel	char *name, *p, *value;
511223306Smarcel	struct group *grent;
512223306Smarcel	struct passwd *pwent;
513223306Smarcel	struct stat *st, sb;
514223306Smarcel	intmax_t num;
515223306Smarcel	u_long flset, flclr;
516223306Smarcel	int error, istemp, type;
517223306Smarcel
518223306Smarcel	st = &node->inode->st;
519223306Smarcel	do {
520223306Smarcel		error = skip_over(fp, " \t");
521223306Smarcel		if (error)
522223306Smarcel			break;
523223306Smarcel
524223306Smarcel		error = read_word(fp, keyword, sizeof(keyword));
525223306Smarcel		if (error)
526223306Smarcel			break;
527223306Smarcel
528223306Smarcel		if (keyword[0] == '\0')
529223306Smarcel			break;
530223306Smarcel
531223306Smarcel		value = strchr(keyword, '=');
532223306Smarcel		if (value != NULL)
533223306Smarcel			*value++ = '\0';
534223306Smarcel
535223306Smarcel		/*
536223306Smarcel		 * We use EINVAL, ENOATTR, ENOSYS and ENXIO to signal
537223306Smarcel		 * certain conditions:
538223306Smarcel		 *   EINVAL -	Value provided for a keyword that does
539223306Smarcel		 *		not take a value. The value is ignored.
540223306Smarcel		 *   ENOATTR -	Value missing for a keyword that needs
541223306Smarcel		 *		a value. The keyword is ignored.
542223306Smarcel		 *   ENOSYS -	Unsupported keyword encountered. The
543223306Smarcel		 *		keyword is ignored.
544223306Smarcel		 *   ENXIO -	Value provided for a keyword that does
545223306Smarcel		 *		not take a value. The value is ignored.
546223306Smarcel		 */
547223306Smarcel		switch (keyword[0]) {
548223306Smarcel		case 'c':
549223306Smarcel			if (strcmp(keyword, "contents") == 0) {
550223306Smarcel				if (value == NULL) {
551223306Smarcel					error = ENOATTR;
552223306Smarcel					break;
553223306Smarcel				}
554223306Smarcel				node->contents = strdup(value);
555223306Smarcel			} else
556223306Smarcel				error = ENOSYS;
557223306Smarcel			break;
558223306Smarcel		case 'f':
559223306Smarcel			if (strcmp(keyword, "flags") == 0) {
560223306Smarcel				if (value == NULL) {
561223306Smarcel					error = ENOATTR;
562223306Smarcel					break;
563223306Smarcel				}
564223306Smarcel				flset = flclr = 0;
565223306Smarcel				if (!strtofflags(&value, &flset, &flclr)) {
566223306Smarcel					st->st_flags &= ~flclr;
567223306Smarcel					st->st_flags |= flset;
568223306Smarcel				} else
569223306Smarcel					error = errno;
570223306Smarcel			} else
571223306Smarcel				error = ENOSYS;
572223306Smarcel			break;
573223306Smarcel		case 'g':
574223306Smarcel			if (strcmp(keyword, "gid") == 0) {
575223306Smarcel				if (value == NULL) {
576223306Smarcel					error = ENOATTR;
577223306Smarcel					break;
578223306Smarcel				}
579223306Smarcel				error = read_number(value, 10, &num,
580223306Smarcel				    0, UINT_MAX);
581223306Smarcel				if (!error)
582223306Smarcel					st->st_gid = num;
583223306Smarcel			} else if (strcmp(keyword, "gname") == 0) {
584223306Smarcel				if (value == NULL) {
585223306Smarcel					error = ENOATTR;
586223306Smarcel					break;
587223306Smarcel				}
588223306Smarcel				grent = getgrnam(value);
589223306Smarcel				if (grent != NULL)
590223306Smarcel					st->st_gid = grent->gr_gid;
591223306Smarcel				else
592223306Smarcel					error = errno;
593223306Smarcel			} else
594223306Smarcel				error = ENOSYS;
595223306Smarcel			break;
596223306Smarcel		case 'l':
597223306Smarcel			if (strcmp(keyword, "link") == 0) {
598223306Smarcel				if (value == NULL) {
599223306Smarcel					error = ENOATTR;
600223306Smarcel					break;
601223306Smarcel				}
602223306Smarcel				node->symlink = strdup(value);
603223306Smarcel			} else
604223306Smarcel				error = ENOSYS;
605223306Smarcel			break;
606223306Smarcel		case 'm':
607223306Smarcel			if (strcmp(keyword, "mode") == 0) {
608223306Smarcel				if (value == NULL) {
609223306Smarcel					error = ENOATTR;
610223306Smarcel					break;
611223306Smarcel				}
612223306Smarcel				if (value[0] >= '0' && value[0] <= '9') {
613223306Smarcel					error = read_number(value, 8, &num,
614223306Smarcel					    0, 07777);
615223306Smarcel					if (!error) {
616223306Smarcel						st->st_mode &= S_IFMT;
617223306Smarcel						st->st_mode |= num;
618223306Smarcel					}
619223306Smarcel				} else {
620223306Smarcel					/* Symbolic mode not supported. */
621223306Smarcel					error = EINVAL;
622223306Smarcel					break;
623223306Smarcel				}
624223306Smarcel			} else
625223306Smarcel				error = ENOSYS;
626223306Smarcel			break;
627223306Smarcel		case 'o':
628223306Smarcel			if (strcmp(keyword, "optional") == 0) {
629223306Smarcel				if (value != NULL)
630223306Smarcel					error = ENXIO;
631223306Smarcel				node->flags |= FSNODE_F_OPTIONAL;
632223306Smarcel			} else
633223306Smarcel				error = ENOSYS;
634223306Smarcel			break;
635223306Smarcel		case 's':
636223306Smarcel			if (strcmp(keyword, "size") == 0) {
637223306Smarcel				if (value == NULL) {
638223306Smarcel					error = ENOATTR;
639223306Smarcel					break;
640223306Smarcel				}
641223306Smarcel				error = read_number(value, 10, &num,
642223306Smarcel				    0, INTMAX_MAX);
643223306Smarcel				if (!error)
644223306Smarcel					st->st_size = num;
645223306Smarcel			} else
646223306Smarcel				error = ENOSYS;
647223306Smarcel			break;
648223306Smarcel		case 't':
649223306Smarcel			if (strcmp(keyword, "time") == 0) {
650223306Smarcel				if (value == NULL) {
651223306Smarcel					error = ENOATTR;
652223306Smarcel					break;
653223306Smarcel				}
654223306Smarcel				p = strchr(value, '.');
655223306Smarcel				if (p != NULL)
656223306Smarcel					*p++ = '\0';
657223306Smarcel				error = read_number(value, 10, &num, 0,
658223306Smarcel				    INTMAX_MAX);
659223306Smarcel				if (error)
660223306Smarcel					break;
661223306Smarcel				st->st_atime = num;
662223306Smarcel				st->st_ctime = num;
663223306Smarcel				st->st_mtime = num;
664223306Smarcel				error = read_number(p, 10, &num, 0,
665223306Smarcel				    INTMAX_MAX);
666223306Smarcel				if (error)
667223306Smarcel					break;
668223306Smarcel				if (num != 0)
669223306Smarcel					error = EINVAL;
670223306Smarcel			} else if (strcmp(keyword, "type") == 0) {
671223306Smarcel				if (value == NULL) {
672223306Smarcel					error = ENOATTR;
673223306Smarcel					break;
674223306Smarcel				}
675223306Smarcel				if (strcmp(value, "dir") == 0)
676223306Smarcel					node->type = S_IFDIR;
677223306Smarcel				else if (strcmp(value, "file") == 0)
678223306Smarcel					node->type = S_IFREG;
679223306Smarcel				else if (strcmp(value, "link") == 0)
680223306Smarcel					node->type = S_IFLNK;
681223306Smarcel				else
682223306Smarcel					error = EINVAL;
683223306Smarcel			} else
684223306Smarcel				error = ENOSYS;
685223306Smarcel			break;
686223306Smarcel		case 'u':
687223306Smarcel			if (strcmp(keyword, "uid") == 0) {
688223306Smarcel				if (value == NULL) {
689223306Smarcel					error = ENOATTR;
690223306Smarcel					break;
691223306Smarcel				}
692223306Smarcel				error = read_number(value, 10, &num,
693223306Smarcel				    0, UINT_MAX);
694223306Smarcel				if (!error)
695223306Smarcel					st->st_uid = num;
696223306Smarcel			} else if (strcmp(keyword, "uname") == 0) {
697223306Smarcel				if (value == NULL) {
698223306Smarcel					error = ENOATTR;
699223306Smarcel					break;
700223306Smarcel				}
701223306Smarcel				pwent = getpwnam(value);
702223306Smarcel				if (pwent != NULL)
703223306Smarcel					st->st_uid = pwent->pw_uid;
704223306Smarcel				else
705223306Smarcel					error = errno;
706223306Smarcel			} else
707223306Smarcel				error = ENOSYS;
708223306Smarcel			break;
709223306Smarcel		default:
710223306Smarcel			error = ENOSYS;
711223306Smarcel			break;
712223306Smarcel		}
713223306Smarcel
714223306Smarcel		switch (error) {
715223306Smarcel		case EINVAL:
716223306Smarcel			mtree_error("%s: invalid value '%s'", keyword, value);
717223306Smarcel			break;
718223306Smarcel		case ENOATTR:
719223306Smarcel			mtree_error("%s: keyword needs a value", keyword);
720223306Smarcel			break;
721223306Smarcel		case ENOSYS:
722223306Smarcel			mtree_warning("%s: unsupported keyword", keyword);
723223306Smarcel			break;
724223306Smarcel		case ENXIO:
725223306Smarcel			mtree_error("%s: keyword does not take a value",
726223306Smarcel			    keyword);
727223306Smarcel			break;
728223306Smarcel		}
729223306Smarcel	} while (1);
730223306Smarcel
731223306Smarcel	if (error)
732223306Smarcel		return (error);
733223306Smarcel
734223306Smarcel	st->st_mode = (st->st_mode & ~S_IFMT) | node->type;
735223306Smarcel
736223306Smarcel	/* Nothing more to do for the global defaults. */
737223306Smarcel	if (node->name == NULL)
738223306Smarcel		return (0);
739223306Smarcel
740223306Smarcel	/*
741223306Smarcel	 * Be intelligent about the file type.
742223306Smarcel	 */
743223306Smarcel	if (node->contents != NULL) {
744223306Smarcel		if (node->symlink != NULL) {
745223306Smarcel			mtree_error("%s: both link and contents keywords "
746223306Smarcel			    "defined", node->name);
747223306Smarcel			return (0);
748223306Smarcel		}
749223306Smarcel		type = S_IFREG;
750242501Ssjg	} else if (node->type != 0) {
751242501Ssjg		type = node->type;
752242501Ssjg		if (type == S_IFREG) {
753242501Ssjg			/* the named path is the default contents */
754242501Ssjg			node->contents = mtree_file_path(node);
755242501Ssjg		}
756223306Smarcel	} else
757223306Smarcel		type = (node->symlink != NULL) ? S_IFLNK : S_IFDIR;
758223306Smarcel
759223306Smarcel	if (node->type == 0)
760223306Smarcel		node->type = type;
761223306Smarcel
762223306Smarcel	if (node->type != type) {
763223306Smarcel		mtree_error("%s: file type and defined keywords to not match",
764223306Smarcel		    node->name);
765223306Smarcel		return (0);
766223306Smarcel	}
767223306Smarcel
768223306Smarcel	st->st_mode = (st->st_mode & ~S_IFMT) | node->type;
769223306Smarcel
770223306Smarcel	if (node->contents == NULL)
771223306Smarcel		return (0);
772223306Smarcel
773223306Smarcel	name = mtree_resolve(node->contents, &istemp);
774223306Smarcel	if (name == NULL)
775223306Smarcel		return (errno);
776223306Smarcel
777223306Smarcel	if (stat(name, &sb) != 0) {
778223306Smarcel		mtree_error("%s: contents file '%s' not found", node->name,
779223306Smarcel		    name);
780223306Smarcel		free(name);
781223306Smarcel		return (0);
782223306Smarcel	}
783223306Smarcel
784223306Smarcel	free(node->contents);
785223306Smarcel	node->contents = name;
786223306Smarcel	st->st_size = sb.st_size;
787223306Smarcel	return (0);
788223306Smarcel}
789223306Smarcel
790223306Smarcelstatic int
791223306Smarcelread_mtree_command(FILE *fp)
792223306Smarcel{
793223306Smarcel	char cmd[10];
794223306Smarcel	int error;
795223306Smarcel
796223306Smarcel	error = read_word(fp, cmd, sizeof(cmd));
797223306Smarcel	if (error)
798223306Smarcel		goto out;
799223306Smarcel
800223306Smarcel	error = read_mtree_keywords(fp, &mtree_global);
801223306Smarcel
802223306Smarcel out:
803223306Smarcel	skip_to(fp, "\n");
804223306Smarcel	(void)getc(fp);
805223306Smarcel	return (error);
806223306Smarcel}
807223306Smarcel
808223306Smarcelstatic int
809223306Smarcelread_mtree_spec1(FILE *fp, bool def, const char *name)
810223306Smarcel{
811223306Smarcel	fsnode *last, *node, *parent;
812223306Smarcel	u_int type;
813223306Smarcel	int error;
814223306Smarcel
815223306Smarcel	assert(name[0] != '\0');
816223306Smarcel
817223306Smarcel	/*
818223306Smarcel	 * Treat '..' specially, because it only changes our current
819223306Smarcel	 * directory. We don't create a node for it. We simply ignore
820223306Smarcel	 * any keywords that may appear on the line as well.
821223306Smarcel	 * Going up a directory is a little non-obvious. A directory
822223306Smarcel	 * node has a corresponding '.' child. The parent of '.' is
823223306Smarcel	 * not the '.' node of the parent directory, but the directory
824223306Smarcel	 * node within the parent to which the child relates. However,
825223306Smarcel	 * going up a directory means we need to find the '.' node to
826223306Smarcel	 * which the directoy node is linked.  This we can do via the
827223306Smarcel	 * first * pointer, because '.' is always the first entry in a
828223306Smarcel	 * directory.
829223306Smarcel	 */
830223306Smarcel	if (IS_DOTDOT(name)) {
831223306Smarcel		/* This deals with NULL pointers as well. */
832223306Smarcel		if (mtree_current == mtree_root) {
833223306Smarcel			mtree_warning("ignoring .. in root directory");
834223306Smarcel			return (0);
835223306Smarcel		}
836223306Smarcel
837223306Smarcel		node = mtree_current;
838223306Smarcel
839223306Smarcel		assert(node != NULL);
840223306Smarcel		assert(IS_DOT(node->name));
841223306Smarcel		assert(node->first == node);
842223306Smarcel
843223306Smarcel		/* Get the corresponding directory node in the parent. */
844223306Smarcel		node = mtree_current->parent;
845223306Smarcel
846223306Smarcel		assert(node != NULL);
847223306Smarcel		assert(!IS_DOT(node->name));
848223306Smarcel
849223306Smarcel		node = node->first;
850223306Smarcel
851223306Smarcel		assert(node != NULL);
852223306Smarcel		assert(IS_DOT(node->name));
853223306Smarcel		assert(node->first == node);
854223306Smarcel
855223306Smarcel		mtree_current = node;
856223306Smarcel		return (0);
857223306Smarcel	}
858223306Smarcel
859223306Smarcel	/*
860223306Smarcel	 * If we don't have a current directory and the first specification
861223306Smarcel	 * (either implicit or defined) is not '.', then we need to create
862223306Smarcel	 * a '.' node first (using a recursive call).
863223306Smarcel	 */
864223306Smarcel	if (!IS_DOT(name) && mtree_current == NULL) {
865223306Smarcel		error = read_mtree_spec1(fp, false, ".");
866223306Smarcel		if (error)
867223306Smarcel			return (error);
868223306Smarcel	}
869223306Smarcel
870223306Smarcel	/*
871223306Smarcel	 * Lookup the name in the current directory (if we have a current
872223306Smarcel	 * directory) to make sure we do not create multiple nodes for the
873223306Smarcel	 * same component. For non-definitions, if we find a node with the
874223306Smarcel	 * same name, simply change the current directory. For definitions
875223306Smarcel	 * more happens.
876223306Smarcel	 */
877223306Smarcel	last = NULL;
878223306Smarcel	node = mtree_current;
879223306Smarcel	while (node != NULL) {
880223306Smarcel		assert(node->first == mtree_current);
881223306Smarcel
882223306Smarcel		if (strcmp(name, node->name) == 0) {
883223306Smarcel			if (def == true) {
884223306Smarcel				mtree_error("duplicate definition of %s",
885223306Smarcel				    name);
886223306Smarcel				return (0);
887223306Smarcel			}
888223306Smarcel
889223306Smarcel			if (node->type != S_IFDIR) {
890223306Smarcel				mtree_error("%s is not a directory", name);
891223306Smarcel				return (0);
892223306Smarcel			}
893223306Smarcel
894223306Smarcel			assert(!IS_DOT(name));
895223306Smarcel
896223306Smarcel			node = node->child;
897223306Smarcel
898223306Smarcel			assert(node != NULL);
899223306Smarcel			assert(IS_DOT(node->name));
900223306Smarcel
901223306Smarcel			mtree_current = node;
902223306Smarcel			return (0);
903223306Smarcel		}
904223306Smarcel
905223306Smarcel		last = node;
906223306Smarcel		node = last->next;
907223306Smarcel	}
908223306Smarcel
909223306Smarcel	parent = (mtree_current != NULL) ? mtree_current->parent : NULL;
910223306Smarcel	type = (def == false || IS_DOT(name)) ? S_IFDIR : 0;
911223306Smarcel	node = create_node(name, type, parent, &mtree_global);
912223306Smarcel	if (node == NULL)
913223306Smarcel		return (ENOMEM);
914223306Smarcel
915223306Smarcel	if (def == true) {
916223306Smarcel		error = read_mtree_keywords(fp, node);
917223306Smarcel		if (error) {
918223306Smarcel			destroy_node(node);
919223306Smarcel			return (error);
920223306Smarcel		}
921223306Smarcel	}
922223306Smarcel
923223306Smarcel	node->first = (mtree_current != NULL) ? mtree_current : node;
924223306Smarcel
925223306Smarcel	if (last != NULL)
926223306Smarcel		last->next = node;
927223306Smarcel
928223306Smarcel	if (node->type != S_IFDIR)
929223306Smarcel		return (0);
930223306Smarcel
931223306Smarcel	if (!IS_DOT(node->name)) {
932223306Smarcel		parent = node;
933223306Smarcel		node = create_node(".", S_IFDIR, parent, parent);
934223306Smarcel		if (node == NULL) {
935223306Smarcel			last->next = NULL;
936223306Smarcel			destroy_node(parent);
937223306Smarcel			return (ENOMEM);
938223306Smarcel		}
939223306Smarcel		parent->child = node;
940223306Smarcel		node->first = node;
941223306Smarcel	}
942223306Smarcel
943223306Smarcel	assert(node != NULL);
944223306Smarcel	assert(IS_DOT(node->name));
945223306Smarcel	assert(node->first == node);
946223306Smarcel
947223306Smarcel	mtree_current = node;
948223306Smarcel	if (mtree_root == NULL)
949223306Smarcel		mtree_root = node;
950223306Smarcel
951223306Smarcel	return (0);
952223306Smarcel}
953223306Smarcel
954223306Smarcelstatic int
955223306Smarcelread_mtree_spec(FILE *fp)
956223306Smarcel{
957223306Smarcel	char pathspec[PATH_MAX];
958223306Smarcel	char *cp;
959223306Smarcel	int error;
960223306Smarcel
961223306Smarcel	error = read_word(fp, pathspec, sizeof(pathspec));
962223306Smarcel	if (error)
963223306Smarcel		goto out;
964223306Smarcel
965223306Smarcel	cp = strchr(pathspec, '/');
966223306Smarcel	if (cp != NULL) {
967223306Smarcel		/* Absolute pathname */
968223306Smarcel		mtree_current = mtree_root;
969223306Smarcel
970223306Smarcel		do {
971223306Smarcel			*cp++ = '\0';
972223306Smarcel
973223306Smarcel			/* Disallow '.' and '..' as components. */
974223306Smarcel			if (IS_DOT(pathspec) || IS_DOTDOT(pathspec)) {
975223306Smarcel				mtree_error("absolute path cannot contain . "
976223306Smarcel				    "or .. components");
977223306Smarcel				goto out;
978223306Smarcel			}
979223306Smarcel
980223306Smarcel			/* Ignore multiple adjacent slashes. */
981223306Smarcel			if (pathspec[0] != '\0')
982223306Smarcel				error = read_mtree_spec1(fp, false, pathspec);
983223306Smarcel			memmove(pathspec, cp, strlen(cp) + 1);
984223306Smarcel			cp = strchr(pathspec, '/');
985223306Smarcel		} while (!error && cp != NULL);
986223306Smarcel
987223306Smarcel		/* Disallow '.' and '..' as the last component. */
988223306Smarcel		if (!error && (IS_DOT(pathspec) || IS_DOTDOT(pathspec))) {
989223306Smarcel			mtree_error("absolute path cannot contain . or .. "
990223306Smarcel			    "components");
991223306Smarcel			goto out;
992223306Smarcel		}
993223306Smarcel	}
994223306Smarcel
995223306Smarcel	/* Ignore absolute specfications that end with a slash. */
996223306Smarcel	if (!error && pathspec[0] != '\0')
997223306Smarcel		error = read_mtree_spec1(fp, true, pathspec);
998223306Smarcel
999223306Smarcel out:
1000223306Smarcel	skip_to(fp, "\n");
1001223306Smarcel	(void)getc(fp);
1002223306Smarcel	return (error);
1003223306Smarcel}
1004223306Smarcel
1005223306Smarcelfsnode *
1006223306Smarcelread_mtree(const char *fname, fsnode *node)
1007223306Smarcel{
1008223306Smarcel	struct mtree_fileinfo *fi;
1009223306Smarcel	FILE *fp;
1010223306Smarcel	int c, error;
1011223306Smarcel
1012223306Smarcel	/* We do not yet support nesting... */
1013223306Smarcel	assert(node == NULL);
1014223306Smarcel
1015223306Smarcel	if (strcmp(fname, "-") == 0)
1016223306Smarcel		fp = stdin;
1017223306Smarcel	else {
1018223306Smarcel		fp = fopen(fname, "r");
1019223306Smarcel		if (fp == NULL)
1020223306Smarcel			err(1, "Can't open `%s'", fname);
1021223306Smarcel	}
1022223306Smarcel
1023223306Smarcel	error = mtree_file_push(fname, fp);
1024223306Smarcel	if (error)
1025223306Smarcel		goto out;
1026223306Smarcel
1027223306Smarcel	bzero(&mtree_global, sizeof(mtree_global));
1028223306Smarcel	bzero(&mtree_global_inode, sizeof(mtree_global_inode));
1029223306Smarcel	mtree_global.inode = &mtree_global_inode;
1030223306Smarcel	mtree_global_inode.nlink = 1;
1031223306Smarcel	mtree_global_inode.st.st_atime = mtree_global_inode.st.st_ctime =
1032223306Smarcel	    mtree_global_inode.st.st_mtime = time(NULL);
1033223306Smarcel	errors = warnings = 0;
1034223306Smarcel
1035223306Smarcel	setgroupent(1);
1036223306Smarcel	setpassent(1);
1037223306Smarcel
1038223306Smarcel	mtree_root = node;
1039223306Smarcel	mtree_current = node;
1040223306Smarcel	do {
1041223306Smarcel		/* Start of a new line... */
1042223306Smarcel		fi = SLIST_FIRST(&mtree_fileinfo);
1043223306Smarcel		fi->line++;
1044223306Smarcel
1045223306Smarcel		error = skip_over(fp, " \t");
1046223306Smarcel		if (error)
1047223306Smarcel			break;
1048223306Smarcel
1049223306Smarcel		c = getc(fp);
1050223306Smarcel		if (c == EOF) {
1051223306Smarcel			error = ferror(fp) ? errno : -1;
1052223306Smarcel			break;
1053223306Smarcel		}
1054223306Smarcel
1055223306Smarcel		switch (c) {
1056223306Smarcel		case '\n':		/* empty line */
1057223306Smarcel			error = 0;
1058223306Smarcel			break;
1059223306Smarcel		case '#':		/* comment -- skip to end of line. */
1060223306Smarcel			error = skip_to(fp, "\n");
1061223306Smarcel			if (!error)
1062223306Smarcel				(void)getc(fp);
1063223306Smarcel			break;
1064223306Smarcel		case '/':		/* special commands */
1065223306Smarcel			error = read_mtree_command(fp);
1066223306Smarcel			break;
1067223306Smarcel		default:		/* specification */
1068223306Smarcel			ungetc(c, fp);
1069223306Smarcel			error = read_mtree_spec(fp);
1070223306Smarcel			break;
1071223306Smarcel		}
1072223306Smarcel	} while (!error);
1073223306Smarcel
1074223306Smarcel	endpwent();
1075223306Smarcel	endgrent();
1076223306Smarcel
1077223306Smarcel	if (error <= 0 && (errors || warnings)) {
1078223306Smarcel		warnx("%u error(s) and %u warning(s) in mtree manifest",
1079223306Smarcel		    errors, warnings);
1080223306Smarcel		if (errors)
1081223306Smarcel			exit(1);
1082223306Smarcel	}
1083223306Smarcel
1084223306Smarcel out:
1085223306Smarcel	if (error > 0)
1086223306Smarcel		errc(1, error, "Error reading mtree file");
1087223306Smarcel
1088223306Smarcel	if (fp != stdin)
1089223306Smarcel		fclose(fp);
1090223306Smarcel
1091223306Smarcel	if (mtree_root != NULL)
1092223306Smarcel		return (mtree_root);
1093223306Smarcel
1094223306Smarcel	/* Handle empty specifications. */
1095223306Smarcel	node = create_node(".", S_IFDIR, NULL, &mtree_global);
1096223306Smarcel	node->first = node;
1097223306Smarcel	return (node);
1098223306Smarcel}
1099