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: releng/11.0/usr.sbin/makefs/mtree.c 299953 2016-05-16 16:16:46Z truckman $");
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;
153299953Struckman	for (pnode = node->parent; pnode && depth < MAKEFS_MAX_TREE_DEPTH - 1;
154242501Ssjg	     pnode = pnode->parent) {
155242501Ssjg		if (strcmp(pnode->name, ".") == 0)
156242501Ssjg			break;
157242501Ssjg		rp[++depth] = pnode->name;
158242501Ssjg	}
159242501Ssjg
160264186Smarcel	sb = sbuf_new_auto();
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;
184287417Sdelphij	char *res, *var = NULL;
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);
287287417Sdelphij		var = NULL;
288223306Smarcel	}
289223306Smarcel
290287417Sdelphij	free(var);
291223306Smarcel	sbuf_finish(sb);
292223306Smarcel	res = (error == 0) ? strdup(sbuf_data(sb)) : NULL;
293223306Smarcel	sbuf_delete(sb);
294223306Smarcel	if (res == NULL)
295223306Smarcel		errno = ENOMEM;
296223306Smarcel	return (res);
297223306Smarcel}
298223306Smarcel
299223306Smarcelstatic int
300223306Smarcelskip_over(FILE *fp, const char *cs)
301223306Smarcel{
302223306Smarcel	int c;
303223306Smarcel
304223306Smarcel	c = getc(fp);
305223306Smarcel	while (c != EOF && strchr(cs, c) != NULL)
306223306Smarcel		c = getc(fp);
307223306Smarcel	if (c != EOF) {
308223306Smarcel		ungetc(c, fp);
309223306Smarcel		return (0);
310223306Smarcel	}
311223306Smarcel	return (ferror(fp) ? errno : -1);
312223306Smarcel}
313223306Smarcel
314223306Smarcelstatic int
315223306Smarcelskip_to(FILE *fp, const char *cs)
316223306Smarcel{
317223306Smarcel	int c;
318223306Smarcel
319223306Smarcel	c = getc(fp);
320223306Smarcel	while (c != EOF && strchr(cs, c) == NULL)
321223306Smarcel		c = getc(fp);
322223306Smarcel	if (c != EOF) {
323223306Smarcel		ungetc(c, fp);
324223306Smarcel		return (0);
325223306Smarcel	}
326223306Smarcel	return (ferror(fp) ? errno : -1);
327223306Smarcel}
328223306Smarcel
329223306Smarcelstatic int
330223306Smarcelread_word(FILE *fp, char *buf, size_t bufsz)
331223306Smarcel{
332223306Smarcel	struct mtree_fileinfo *fi;
333223306Smarcel	size_t idx, qidx;
334223306Smarcel	int c, done, error, esc, qlvl;
335223306Smarcel
336223306Smarcel	if (bufsz == 0)
337223306Smarcel		return (EINVAL);
338223306Smarcel
339223306Smarcel	done = 0;
340223306Smarcel	esc = 0;
341223306Smarcel	idx = 0;
342223306Smarcel	qidx = -1;
343223306Smarcel	qlvl = 0;
344223306Smarcel	do {
345223306Smarcel		c = getc(fp);
346223306Smarcel		switch (c) {
347223306Smarcel		case EOF:
348223306Smarcel			buf[idx] = '\0';
349223306Smarcel			error = ferror(fp) ? errno : -1;
350223306Smarcel			if (error == -1)
351223306Smarcel				mtree_error("unexpected end of file");
352223306Smarcel			return (error);
353262749Ssjg		case '#':		/* comment -- skip to end of line. */
354262749Ssjg			if (!esc) {
355262749Ssjg				error = skip_to(fp, "\n");
356262749Ssjg				if (!error)
357262749Ssjg					continue;
358262749Ssjg			}
359262749Ssjg			break;
360223306Smarcel		case '\\':
361223306Smarcel			esc++;
362223306Smarcel			if (esc == 1)
363223306Smarcel				continue;
364223306Smarcel			break;
365223306Smarcel		case '`':
366223306Smarcel		case '\'':
367223306Smarcel		case '"':
368223306Smarcel			if (esc)
369223306Smarcel				break;
370223306Smarcel			if (qlvl == 0) {
371223306Smarcel				qlvl++;
372223306Smarcel				qidx = idx;
373223306Smarcel			} else if (c == buf[qidx]) {
374223306Smarcel				qlvl--;
375223306Smarcel				if (qlvl > 0) {
376223306Smarcel					do {
377223306Smarcel						qidx--;
378223306Smarcel					} while (buf[qidx] != '`' &&
379223306Smarcel					    buf[qidx] != '\'' &&
380223306Smarcel					    buf[qidx] != '"');
381223306Smarcel				} else
382223306Smarcel					qidx = -1;
383223306Smarcel			} else {
384223306Smarcel				qlvl++;
385223306Smarcel				qidx = idx;
386223306Smarcel			}
387223306Smarcel			break;
388223306Smarcel		case ' ':
389223306Smarcel		case '\t':
390223306Smarcel		case '\n':
391223306Smarcel			if (!esc && qlvl == 0) {
392223306Smarcel				ungetc(c, fp);
393223306Smarcel				c = '\0';
394223306Smarcel				done = 1;
395223306Smarcel				break;
396223306Smarcel			}
397223306Smarcel			if (c == '\n') {
398223306Smarcel				/*
399223306Smarcel				 * We going to eat the newline ourselves.
400223306Smarcel				 */
401223306Smarcel				if (qlvl > 0)
402223306Smarcel					mtree_warning("quoted word straddles "
403223306Smarcel					    "onto next line.");
404223306Smarcel				fi = SLIST_FIRST(&mtree_fileinfo);
405223306Smarcel				fi->line++;
406223306Smarcel			}
407223306Smarcel			break;
408223306Smarcel		case 'a':
409223306Smarcel			if (esc)
410223306Smarcel				c = '\a';
411223306Smarcel			break;
412223306Smarcel		case 'b':
413223306Smarcel			if (esc)
414223306Smarcel				c = '\b';
415223306Smarcel			break;
416223306Smarcel		case 'f':
417223306Smarcel			if (esc)
418223306Smarcel				c = '\f';
419223306Smarcel			break;
420223306Smarcel		case 'n':
421223306Smarcel			if (esc)
422223306Smarcel				c = '\n';
423223306Smarcel			break;
424223306Smarcel		case 'r':
425223306Smarcel			if (esc)
426223306Smarcel				c = '\r';
427223306Smarcel			break;
428223306Smarcel		case 't':
429223306Smarcel			if (esc)
430223306Smarcel				c = '\t';
431223306Smarcel			break;
432223306Smarcel		case 'v':
433223306Smarcel			if (esc)
434223306Smarcel				c = '\v';
435223306Smarcel			break;
436223306Smarcel		}
437223306Smarcel		buf[idx++] = c;
438223306Smarcel		esc = 0;
439223306Smarcel	} while (idx < bufsz && !done);
440223306Smarcel
441223306Smarcel	if (idx >= bufsz) {
442223306Smarcel		mtree_error("word too long to fit buffer (max %zu characters)",
443223306Smarcel		    bufsz);
444223306Smarcel		skip_to(fp, " \t\n");
445223306Smarcel	}
446223306Smarcel	return (0);
447223306Smarcel}
448223306Smarcel
449223306Smarcelstatic fsnode *
450223306Smarcelcreate_node(const char *name, u_int type, fsnode *parent, fsnode *global)
451223306Smarcel{
452223306Smarcel	fsnode *n;
453223306Smarcel
454223306Smarcel	n = calloc(1, sizeof(*n));
455223306Smarcel	if (n == NULL)
456223306Smarcel		return (NULL);
457223306Smarcel
458223306Smarcel	n->name = strdup(name);
459223306Smarcel	if (n->name == NULL) {
460223306Smarcel		free(n);
461223306Smarcel		return (NULL);
462223306Smarcel	}
463223306Smarcel
464223306Smarcel	n->type = (type == 0) ? global->type : type;
465223306Smarcel	n->parent = parent;
466223306Smarcel
467223306Smarcel	n->inode = calloc(1, sizeof(*n->inode));
468223306Smarcel	if (n->inode == NULL) {
469223306Smarcel		free(n->name);
470223306Smarcel		free(n);
471223306Smarcel		return (NULL);
472223306Smarcel	}
473223306Smarcel
474223306Smarcel	/* Assign global options/defaults. */
475223306Smarcel	bcopy(global->inode, n->inode, sizeof(*n->inode));
476223306Smarcel	n->inode->st.st_mode = (n->inode->st.st_mode & ~S_IFMT) | n->type;
477223306Smarcel
478223306Smarcel	if (n->type == S_IFLNK)
479223306Smarcel		n->symlink = global->symlink;
480223306Smarcel	else if (n->type == S_IFREG)
481223306Smarcel		n->contents = global->contents;
482223306Smarcel
483223306Smarcel	return (n);
484223306Smarcel}
485223306Smarcel
486223306Smarcelstatic void
487223306Smarceldestroy_node(fsnode *n)
488223306Smarcel{
489223306Smarcel
490223306Smarcel	assert(n != NULL);
491223306Smarcel	assert(n->name != NULL);
492223306Smarcel	assert(n->inode != NULL);
493223306Smarcel
494223306Smarcel	free(n->inode);
495223306Smarcel	free(n->name);
496223306Smarcel	free(n);
497223306Smarcel}
498223306Smarcel
499223306Smarcelstatic int
500223306Smarcelread_number(const char *tok, u_int base, intmax_t *res, intmax_t min,
501223306Smarcel    intmax_t max)
502223306Smarcel{
503223306Smarcel	char *end;
504223306Smarcel	intmax_t val;
505223306Smarcel
506223306Smarcel	val = strtoimax(tok, &end, base);
507223306Smarcel	if (end == tok || end[0] != '\0')
508223306Smarcel		return (EINVAL);
509223306Smarcel	if (val < min || val > max)
510223306Smarcel		return (EDOM);
511223306Smarcel	*res = val;
512223306Smarcel	return (0);
513223306Smarcel}
514223306Smarcel
515223306Smarcelstatic int
516223306Smarcelread_mtree_keywords(FILE *fp, fsnode *node)
517223306Smarcel{
518223306Smarcel	char keyword[PATH_MAX];
519223306Smarcel	char *name, *p, *value;
520247042Sbrooks	gid_t gid;
521247042Sbrooks	uid_t uid;
522223306Smarcel	struct stat *st, sb;
523223306Smarcel	intmax_t num;
524223306Smarcel	u_long flset, flclr;
525223306Smarcel	int error, istemp, type;
526223306Smarcel
527223306Smarcel	st = &node->inode->st;
528223306Smarcel	do {
529223306Smarcel		error = skip_over(fp, " \t");
530223306Smarcel		if (error)
531223306Smarcel			break;
532223306Smarcel
533223306Smarcel		error = read_word(fp, keyword, sizeof(keyword));
534223306Smarcel		if (error)
535223306Smarcel			break;
536223306Smarcel
537223306Smarcel		if (keyword[0] == '\0')
538223306Smarcel			break;
539223306Smarcel
540223306Smarcel		value = strchr(keyword, '=');
541223306Smarcel		if (value != NULL)
542223306Smarcel			*value++ = '\0';
543223306Smarcel
544223306Smarcel		/*
545223306Smarcel		 * We use EINVAL, ENOATTR, ENOSYS and ENXIO to signal
546223306Smarcel		 * certain conditions:
547223306Smarcel		 *   EINVAL -	Value provided for a keyword that does
548223306Smarcel		 *		not take a value. The value is ignored.
549223306Smarcel		 *   ENOATTR -	Value missing for a keyword that needs
550223306Smarcel		 *		a value. The keyword is ignored.
551223306Smarcel		 *   ENOSYS -	Unsupported keyword encountered. The
552223306Smarcel		 *		keyword is ignored.
553223306Smarcel		 *   ENXIO -	Value provided for a keyword that does
554223306Smarcel		 *		not take a value. The value is ignored.
555223306Smarcel		 */
556223306Smarcel		switch (keyword[0]) {
557223306Smarcel		case 'c':
558223306Smarcel			if (strcmp(keyword, "contents") == 0) {
559223306Smarcel				if (value == NULL) {
560223306Smarcel					error = ENOATTR;
561223306Smarcel					break;
562223306Smarcel				}
563223306Smarcel				node->contents = strdup(value);
564223306Smarcel			} else
565223306Smarcel				error = ENOSYS;
566223306Smarcel			break;
567223306Smarcel		case 'f':
568223306Smarcel			if (strcmp(keyword, "flags") == 0) {
569223306Smarcel				if (value == NULL) {
570223306Smarcel					error = ENOATTR;
571223306Smarcel					break;
572223306Smarcel				}
573223306Smarcel				flset = flclr = 0;
574223306Smarcel				if (!strtofflags(&value, &flset, &flclr)) {
575223306Smarcel					st->st_flags &= ~flclr;
576223306Smarcel					st->st_flags |= flset;
577223306Smarcel				} else
578223306Smarcel					error = errno;
579223306Smarcel			} else
580223306Smarcel				error = ENOSYS;
581223306Smarcel			break;
582223306Smarcel		case 'g':
583223306Smarcel			if (strcmp(keyword, "gid") == 0) {
584223306Smarcel				if (value == NULL) {
585223306Smarcel					error = ENOATTR;
586223306Smarcel					break;
587223306Smarcel				}
588223306Smarcel				error = read_number(value, 10, &num,
589223306Smarcel				    0, UINT_MAX);
590223306Smarcel				if (!error)
591223306Smarcel					st->st_gid = num;
592223306Smarcel			} else if (strcmp(keyword, "gname") == 0) {
593223306Smarcel				if (value == NULL) {
594223306Smarcel					error = ENOATTR;
595223306Smarcel					break;
596223306Smarcel				}
597247042Sbrooks				if (gid_from_group(value, &gid) == 0)
598247042Sbrooks					st->st_gid = gid;
599223306Smarcel				else
600247042Sbrooks					error = EINVAL;
601223306Smarcel			} else
602223306Smarcel				error = ENOSYS;
603223306Smarcel			break;
604223306Smarcel		case 'l':
605223306Smarcel			if (strcmp(keyword, "link") == 0) {
606223306Smarcel				if (value == NULL) {
607223306Smarcel					error = ENOATTR;
608223306Smarcel					break;
609223306Smarcel				}
610223306Smarcel				node->symlink = strdup(value);
611223306Smarcel			} else
612223306Smarcel				error = ENOSYS;
613223306Smarcel			break;
614223306Smarcel		case 'm':
615223306Smarcel			if (strcmp(keyword, "mode") == 0) {
616223306Smarcel				if (value == NULL) {
617223306Smarcel					error = ENOATTR;
618223306Smarcel					break;
619223306Smarcel				}
620223306Smarcel				if (value[0] >= '0' && value[0] <= '9') {
621223306Smarcel					error = read_number(value, 8, &num,
622223306Smarcel					    0, 07777);
623223306Smarcel					if (!error) {
624223306Smarcel						st->st_mode &= S_IFMT;
625223306Smarcel						st->st_mode |= num;
626223306Smarcel					}
627223306Smarcel				} else {
628223306Smarcel					/* Symbolic mode not supported. */
629223306Smarcel					error = EINVAL;
630223306Smarcel					break;
631223306Smarcel				}
632223306Smarcel			} else
633223306Smarcel				error = ENOSYS;
634223306Smarcel			break;
635223306Smarcel		case 'o':
636223306Smarcel			if (strcmp(keyword, "optional") == 0) {
637223306Smarcel				if (value != NULL)
638223306Smarcel					error = ENXIO;
639223306Smarcel				node->flags |= FSNODE_F_OPTIONAL;
640223306Smarcel			} else
641223306Smarcel				error = ENOSYS;
642223306Smarcel			break;
643223306Smarcel		case 's':
644223306Smarcel			if (strcmp(keyword, "size") == 0) {
645223306Smarcel				if (value == NULL) {
646223306Smarcel					error = ENOATTR;
647223306Smarcel					break;
648223306Smarcel				}
649223306Smarcel				error = read_number(value, 10, &num,
650223306Smarcel				    0, INTMAX_MAX);
651223306Smarcel				if (!error)
652223306Smarcel					st->st_size = num;
653223306Smarcel			} else
654223306Smarcel				error = ENOSYS;
655223306Smarcel			break;
656223306Smarcel		case 't':
657223306Smarcel			if (strcmp(keyword, "time") == 0) {
658223306Smarcel				if (value == NULL) {
659223306Smarcel					error = ENOATTR;
660223306Smarcel					break;
661223306Smarcel				}
662223306Smarcel				p = strchr(value, '.');
663223306Smarcel				if (p != NULL)
664223306Smarcel					*p++ = '\0';
665223306Smarcel				error = read_number(value, 10, &num, 0,
666223306Smarcel				    INTMAX_MAX);
667223306Smarcel				if (error)
668223306Smarcel					break;
669223306Smarcel				st->st_atime = num;
670223306Smarcel				st->st_ctime = num;
671223306Smarcel				st->st_mtime = num;
672264186Smarcel				if (p == NULL)
673264186Smarcel					break;
674223306Smarcel				error = read_number(p, 10, &num, 0,
675223306Smarcel				    INTMAX_MAX);
676223306Smarcel				if (error)
677223306Smarcel					break;
678223306Smarcel				if (num != 0)
679223306Smarcel					error = EINVAL;
680223306Smarcel			} else if (strcmp(keyword, "type") == 0) {
681223306Smarcel				if (value == NULL) {
682223306Smarcel					error = ENOATTR;
683223306Smarcel					break;
684223306Smarcel				}
685223306Smarcel				if (strcmp(value, "dir") == 0)
686223306Smarcel					node->type = S_IFDIR;
687223306Smarcel				else if (strcmp(value, "file") == 0)
688223306Smarcel					node->type = S_IFREG;
689223306Smarcel				else if (strcmp(value, "link") == 0)
690223306Smarcel					node->type = S_IFLNK;
691223306Smarcel				else
692223306Smarcel					error = EINVAL;
693223306Smarcel			} else
694223306Smarcel				error = ENOSYS;
695223306Smarcel			break;
696223306Smarcel		case 'u':
697223306Smarcel			if (strcmp(keyword, "uid") == 0) {
698223306Smarcel				if (value == NULL) {
699223306Smarcel					error = ENOATTR;
700223306Smarcel					break;
701223306Smarcel				}
702223306Smarcel				error = read_number(value, 10, &num,
703223306Smarcel				    0, UINT_MAX);
704223306Smarcel				if (!error)
705223306Smarcel					st->st_uid = num;
706223306Smarcel			} else if (strcmp(keyword, "uname") == 0) {
707223306Smarcel				if (value == NULL) {
708223306Smarcel					error = ENOATTR;
709223306Smarcel					break;
710223306Smarcel				}
711247042Sbrooks				if (uid_from_user(value, &uid) == 0)
712247042Sbrooks					st->st_uid = uid;
713223306Smarcel				else
714247042Sbrooks					error = EINVAL;
715223306Smarcel			} else
716223306Smarcel				error = ENOSYS;
717223306Smarcel			break;
718223306Smarcel		default:
719223306Smarcel			error = ENOSYS;
720223306Smarcel			break;
721223306Smarcel		}
722223306Smarcel
723223306Smarcel		switch (error) {
724223306Smarcel		case EINVAL:
725223306Smarcel			mtree_error("%s: invalid value '%s'", keyword, value);
726223306Smarcel			break;
727223306Smarcel		case ENOATTR:
728223306Smarcel			mtree_error("%s: keyword needs a value", keyword);
729223306Smarcel			break;
730223306Smarcel		case ENOSYS:
731223306Smarcel			mtree_warning("%s: unsupported keyword", keyword);
732223306Smarcel			break;
733223306Smarcel		case ENXIO:
734223306Smarcel			mtree_error("%s: keyword does not take a value",
735223306Smarcel			    keyword);
736223306Smarcel			break;
737223306Smarcel		}
738223306Smarcel	} while (1);
739223306Smarcel
740223306Smarcel	if (error)
741223306Smarcel		return (error);
742223306Smarcel
743223306Smarcel	st->st_mode = (st->st_mode & ~S_IFMT) | node->type;
744223306Smarcel
745223306Smarcel	/* Nothing more to do for the global defaults. */
746223306Smarcel	if (node->name == NULL)
747223306Smarcel		return (0);
748223306Smarcel
749223306Smarcel	/*
750223306Smarcel	 * Be intelligent about the file type.
751223306Smarcel	 */
752223306Smarcel	if (node->contents != NULL) {
753223306Smarcel		if (node->symlink != NULL) {
754223306Smarcel			mtree_error("%s: both link and contents keywords "
755223306Smarcel			    "defined", node->name);
756223306Smarcel			return (0);
757223306Smarcel		}
758223306Smarcel		type = S_IFREG;
759242501Ssjg	} else if (node->type != 0) {
760242501Ssjg		type = node->type;
761242501Ssjg		if (type == S_IFREG) {
762242501Ssjg			/* the named path is the default contents */
763242501Ssjg			node->contents = mtree_file_path(node);
764242501Ssjg		}
765223306Smarcel	} else
766223306Smarcel		type = (node->symlink != NULL) ? S_IFLNK : S_IFDIR;
767223306Smarcel
768223306Smarcel	if (node->type == 0)
769223306Smarcel		node->type = type;
770223306Smarcel
771223306Smarcel	if (node->type != type) {
772223306Smarcel		mtree_error("%s: file type and defined keywords to not match",
773223306Smarcel		    node->name);
774223306Smarcel		return (0);
775223306Smarcel	}
776223306Smarcel
777223306Smarcel	st->st_mode = (st->st_mode & ~S_IFMT) | node->type;
778223306Smarcel
779223306Smarcel	if (node->contents == NULL)
780223306Smarcel		return (0);
781223306Smarcel
782223306Smarcel	name = mtree_resolve(node->contents, &istemp);
783223306Smarcel	if (name == NULL)
784223306Smarcel		return (errno);
785223306Smarcel
786223306Smarcel	if (stat(name, &sb) != 0) {
787223306Smarcel		mtree_error("%s: contents file '%s' not found", node->name,
788223306Smarcel		    name);
789223306Smarcel		free(name);
790223306Smarcel		return (0);
791223306Smarcel	}
792223306Smarcel
793247052Sbrooks	/*
794247052Sbrooks         * Check for hardlinks. If the contents key is used, then the check
795247052Sbrooks         * will only trigger if the contents file is a link even if it is used
796247052Sbrooks         * by more than one file
797247052Sbrooks	 */
798247052Sbrooks	if (sb.st_nlink > 1) {
799247052Sbrooks		fsinode *curino;
800247052Sbrooks
801247052Sbrooks		st->st_ino = sb.st_ino;
802247052Sbrooks		st->st_dev = sb.st_dev;
803247052Sbrooks		curino = link_check(node->inode);
804247052Sbrooks		if (curino != NULL) {
805247052Sbrooks			free(node->inode);
806247052Sbrooks			node->inode = curino;
807247052Sbrooks			node->inode->nlink++;
808247052Sbrooks		}
809247052Sbrooks	}
810247052Sbrooks
811223306Smarcel	free(node->contents);
812223306Smarcel	node->contents = name;
813223306Smarcel	st->st_size = sb.st_size;
814223306Smarcel	return (0);
815223306Smarcel}
816223306Smarcel
817223306Smarcelstatic int
818223306Smarcelread_mtree_command(FILE *fp)
819223306Smarcel{
820223306Smarcel	char cmd[10];
821223306Smarcel	int error;
822223306Smarcel
823223306Smarcel	error = read_word(fp, cmd, sizeof(cmd));
824223306Smarcel	if (error)
825223306Smarcel		goto out;
826223306Smarcel
827223306Smarcel	error = read_mtree_keywords(fp, &mtree_global);
828223306Smarcel
829223306Smarcel out:
830223306Smarcel	skip_to(fp, "\n");
831223306Smarcel	(void)getc(fp);
832223306Smarcel	return (error);
833223306Smarcel}
834223306Smarcel
835223306Smarcelstatic int
836223306Smarcelread_mtree_spec1(FILE *fp, bool def, const char *name)
837223306Smarcel{
838223306Smarcel	fsnode *last, *node, *parent;
839223306Smarcel	u_int type;
840223306Smarcel	int error;
841223306Smarcel
842223306Smarcel	assert(name[0] != '\0');
843223306Smarcel
844223306Smarcel	/*
845223306Smarcel	 * Treat '..' specially, because it only changes our current
846223306Smarcel	 * directory. We don't create a node for it. We simply ignore
847223306Smarcel	 * any keywords that may appear on the line as well.
848223306Smarcel	 * Going up a directory is a little non-obvious. A directory
849223306Smarcel	 * node has a corresponding '.' child. The parent of '.' is
850223306Smarcel	 * not the '.' node of the parent directory, but the directory
851223306Smarcel	 * node within the parent to which the child relates. However,
852223306Smarcel	 * going up a directory means we need to find the '.' node to
853223306Smarcel	 * which the directoy node is linked.  This we can do via the
854223306Smarcel	 * first * pointer, because '.' is always the first entry in a
855223306Smarcel	 * directory.
856223306Smarcel	 */
857223306Smarcel	if (IS_DOTDOT(name)) {
858223306Smarcel		/* This deals with NULL pointers as well. */
859223306Smarcel		if (mtree_current == mtree_root) {
860223306Smarcel			mtree_warning("ignoring .. in root directory");
861223306Smarcel			return (0);
862223306Smarcel		}
863223306Smarcel
864223306Smarcel		node = mtree_current;
865223306Smarcel
866223306Smarcel		assert(node != NULL);
867223306Smarcel		assert(IS_DOT(node->name));
868223306Smarcel		assert(node->first == node);
869223306Smarcel
870223306Smarcel		/* Get the corresponding directory node in the parent. */
871223306Smarcel		node = mtree_current->parent;
872223306Smarcel
873223306Smarcel		assert(node != NULL);
874223306Smarcel		assert(!IS_DOT(node->name));
875223306Smarcel
876223306Smarcel		node = node->first;
877223306Smarcel
878223306Smarcel		assert(node != NULL);
879223306Smarcel		assert(IS_DOT(node->name));
880223306Smarcel		assert(node->first == node);
881223306Smarcel
882223306Smarcel		mtree_current = node;
883223306Smarcel		return (0);
884223306Smarcel	}
885223306Smarcel
886223306Smarcel	/*
887223306Smarcel	 * If we don't have a current directory and the first specification
888223306Smarcel	 * (either implicit or defined) is not '.', then we need to create
889223306Smarcel	 * a '.' node first (using a recursive call).
890223306Smarcel	 */
891223306Smarcel	if (!IS_DOT(name) && mtree_current == NULL) {
892223306Smarcel		error = read_mtree_spec1(fp, false, ".");
893223306Smarcel		if (error)
894223306Smarcel			return (error);
895223306Smarcel	}
896223306Smarcel
897223306Smarcel	/*
898223306Smarcel	 * Lookup the name in the current directory (if we have a current
899223306Smarcel	 * directory) to make sure we do not create multiple nodes for the
900223306Smarcel	 * same component. For non-definitions, if we find a node with the
901223306Smarcel	 * same name, simply change the current directory. For definitions
902223306Smarcel	 * more happens.
903223306Smarcel	 */
904223306Smarcel	last = NULL;
905223306Smarcel	node = mtree_current;
906223306Smarcel	while (node != NULL) {
907223306Smarcel		assert(node->first == mtree_current);
908223306Smarcel
909223306Smarcel		if (strcmp(name, node->name) == 0) {
910223306Smarcel			if (def == true) {
911247041Sbrooks				if (!dupsok)
912247041Sbrooks					mtree_error(
913247041Sbrooks					    "duplicate definition of %s",
914247041Sbrooks					    name);
915247041Sbrooks				else
916247041Sbrooks					mtree_warning(
917247041Sbrooks					    "duplicate definition of %s",
918247041Sbrooks					    name);
919223306Smarcel				return (0);
920223306Smarcel			}
921223306Smarcel
922223306Smarcel			if (node->type != S_IFDIR) {
923223306Smarcel				mtree_error("%s is not a directory", name);
924223306Smarcel				return (0);
925223306Smarcel			}
926223306Smarcel
927223306Smarcel			assert(!IS_DOT(name));
928223306Smarcel
929223306Smarcel			node = node->child;
930223306Smarcel
931223306Smarcel			assert(node != NULL);
932223306Smarcel			assert(IS_DOT(node->name));
933223306Smarcel
934223306Smarcel			mtree_current = node;
935223306Smarcel			return (0);
936223306Smarcel		}
937223306Smarcel
938223306Smarcel		last = node;
939223306Smarcel		node = last->next;
940223306Smarcel	}
941223306Smarcel
942223306Smarcel	parent = (mtree_current != NULL) ? mtree_current->parent : NULL;
943223306Smarcel	type = (def == false || IS_DOT(name)) ? S_IFDIR : 0;
944223306Smarcel	node = create_node(name, type, parent, &mtree_global);
945223306Smarcel	if (node == NULL)
946223306Smarcel		return (ENOMEM);
947223306Smarcel
948223306Smarcel	if (def == true) {
949223306Smarcel		error = read_mtree_keywords(fp, node);
950223306Smarcel		if (error) {
951223306Smarcel			destroy_node(node);
952223306Smarcel			return (error);
953223306Smarcel		}
954223306Smarcel	}
955223306Smarcel
956223306Smarcel	node->first = (mtree_current != NULL) ? mtree_current : node;
957223306Smarcel
958223306Smarcel	if (last != NULL)
959223306Smarcel		last->next = node;
960223306Smarcel
961223306Smarcel	if (node->type != S_IFDIR)
962223306Smarcel		return (0);
963223306Smarcel
964223306Smarcel	if (!IS_DOT(node->name)) {
965223306Smarcel		parent = node;
966223306Smarcel		node = create_node(".", S_IFDIR, parent, parent);
967223306Smarcel		if (node == NULL) {
968223306Smarcel			last->next = NULL;
969223306Smarcel			destroy_node(parent);
970223306Smarcel			return (ENOMEM);
971223306Smarcel		}
972223306Smarcel		parent->child = node;
973223306Smarcel		node->first = node;
974223306Smarcel	}
975223306Smarcel
976223306Smarcel	assert(node != NULL);
977223306Smarcel	assert(IS_DOT(node->name));
978223306Smarcel	assert(node->first == node);
979223306Smarcel
980223306Smarcel	mtree_current = node;
981223306Smarcel	if (mtree_root == NULL)
982223306Smarcel		mtree_root = node;
983223306Smarcel
984223306Smarcel	return (0);
985223306Smarcel}
986223306Smarcel
987223306Smarcelstatic int
988223306Smarcelread_mtree_spec(FILE *fp)
989223306Smarcel{
990223306Smarcel	char pathspec[PATH_MAX];
991223306Smarcel	char *cp;
992223306Smarcel	int error;
993223306Smarcel
994223306Smarcel	error = read_word(fp, pathspec, sizeof(pathspec));
995223306Smarcel	if (error)
996223306Smarcel		goto out;
997223306Smarcel
998223306Smarcel	cp = strchr(pathspec, '/');
999223306Smarcel	if (cp != NULL) {
1000223306Smarcel		/* Absolute pathname */
1001223306Smarcel		mtree_current = mtree_root;
1002223306Smarcel
1003223306Smarcel		do {
1004223306Smarcel			*cp++ = '\0';
1005223306Smarcel
1006247043Sbrooks			/* Disallow '..' as a component. */
1007247043Sbrooks			if (IS_DOTDOT(pathspec)) {
1008247043Sbrooks				mtree_error("absolute path cannot contain "
1009247043Sbrooks				    ".. component");
1010223306Smarcel				goto out;
1011223306Smarcel			}
1012223306Smarcel
1013247043Sbrooks			/* Ignore multiple adjacent slashes and '.'. */
1014247043Sbrooks			if (pathspec[0] != '\0' && !IS_DOT(pathspec))
1015223306Smarcel				error = read_mtree_spec1(fp, false, pathspec);
1016223306Smarcel			memmove(pathspec, cp, strlen(cp) + 1);
1017223306Smarcel			cp = strchr(pathspec, '/');
1018223306Smarcel		} while (!error && cp != NULL);
1019223306Smarcel
1020223306Smarcel		/* Disallow '.' and '..' as the last component. */
1021223306Smarcel		if (!error && (IS_DOT(pathspec) || IS_DOTDOT(pathspec))) {
1022223306Smarcel			mtree_error("absolute path cannot contain . or .. "
1023223306Smarcel			    "components");
1024223306Smarcel			goto out;
1025223306Smarcel		}
1026223306Smarcel	}
1027223306Smarcel
1028223306Smarcel	/* Ignore absolute specfications that end with a slash. */
1029223306Smarcel	if (!error && pathspec[0] != '\0')
1030223306Smarcel		error = read_mtree_spec1(fp, true, pathspec);
1031223306Smarcel
1032223306Smarcel out:
1033223306Smarcel	skip_to(fp, "\n");
1034223306Smarcel	(void)getc(fp);
1035223306Smarcel	return (error);
1036223306Smarcel}
1037223306Smarcel
1038223306Smarcelfsnode *
1039223306Smarcelread_mtree(const char *fname, fsnode *node)
1040223306Smarcel{
1041223306Smarcel	struct mtree_fileinfo *fi;
1042223306Smarcel	FILE *fp;
1043223306Smarcel	int c, error;
1044223306Smarcel
1045223306Smarcel	/* We do not yet support nesting... */
1046223306Smarcel	assert(node == NULL);
1047223306Smarcel
1048223306Smarcel	if (strcmp(fname, "-") == 0)
1049223306Smarcel		fp = stdin;
1050223306Smarcel	else {
1051223306Smarcel		fp = fopen(fname, "r");
1052223306Smarcel		if (fp == NULL)
1053223306Smarcel			err(1, "Can't open `%s'", fname);
1054223306Smarcel	}
1055223306Smarcel
1056223306Smarcel	error = mtree_file_push(fname, fp);
1057223306Smarcel	if (error)
1058223306Smarcel		goto out;
1059223306Smarcel
1060223306Smarcel	bzero(&mtree_global, sizeof(mtree_global));
1061223306Smarcel	bzero(&mtree_global_inode, sizeof(mtree_global_inode));
1062223306Smarcel	mtree_global.inode = &mtree_global_inode;
1063223306Smarcel	mtree_global_inode.nlink = 1;
1064250605Smarcel	mtree_global_inode.st.st_nlink = 1;
1065223306Smarcel	mtree_global_inode.st.st_atime = mtree_global_inode.st.st_ctime =
1066223306Smarcel	    mtree_global_inode.st.st_mtime = time(NULL);
1067223306Smarcel	errors = warnings = 0;
1068223306Smarcel
1069223306Smarcel	setgroupent(1);
1070223306Smarcel	setpassent(1);
1071223306Smarcel
1072223306Smarcel	mtree_root = node;
1073223306Smarcel	mtree_current = node;
1074223306Smarcel	do {
1075223306Smarcel		/* Start of a new line... */
1076223306Smarcel		fi = SLIST_FIRST(&mtree_fileinfo);
1077223306Smarcel		fi->line++;
1078223306Smarcel
1079223306Smarcel		error = skip_over(fp, " \t");
1080223306Smarcel		if (error)
1081223306Smarcel			break;
1082223306Smarcel
1083223306Smarcel		c = getc(fp);
1084223306Smarcel		if (c == EOF) {
1085223306Smarcel			error = ferror(fp) ? errno : -1;
1086223306Smarcel			break;
1087223306Smarcel		}
1088223306Smarcel
1089223306Smarcel		switch (c) {
1090223306Smarcel		case '\n':		/* empty line */
1091223306Smarcel			error = 0;
1092223306Smarcel			break;
1093223306Smarcel		case '#':		/* comment -- skip to end of line. */
1094223306Smarcel			error = skip_to(fp, "\n");
1095223306Smarcel			if (!error)
1096223306Smarcel				(void)getc(fp);
1097223306Smarcel			break;
1098223306Smarcel		case '/':		/* special commands */
1099223306Smarcel			error = read_mtree_command(fp);
1100223306Smarcel			break;
1101223306Smarcel		default:		/* specification */
1102223306Smarcel			ungetc(c, fp);
1103223306Smarcel			error = read_mtree_spec(fp);
1104223306Smarcel			break;
1105223306Smarcel		}
1106223306Smarcel	} while (!error);
1107223306Smarcel
1108223306Smarcel	endpwent();
1109223306Smarcel	endgrent();
1110223306Smarcel
1111223306Smarcel	if (error <= 0 && (errors || warnings)) {
1112223306Smarcel		warnx("%u error(s) and %u warning(s) in mtree manifest",
1113223306Smarcel		    errors, warnings);
1114223306Smarcel		if (errors)
1115223306Smarcel			exit(1);
1116223306Smarcel	}
1117223306Smarcel
1118223306Smarcel out:
1119223306Smarcel	if (error > 0)
1120223306Smarcel		errc(1, error, "Error reading mtree file");
1121223306Smarcel
1122223306Smarcel	if (fp != stdin)
1123223306Smarcel		fclose(fp);
1124223306Smarcel
1125223306Smarcel	if (mtree_root != NULL)
1126223306Smarcel		return (mtree_root);
1127223306Smarcel
1128223306Smarcel	/* Handle empty specifications. */
1129223306Smarcel	node = create_node(".", S_IFDIR, NULL, &mtree_global);
1130223306Smarcel	node->first = node;
1131223306Smarcel	return (node);
1132223306Smarcel}
1133