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$");
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;
511247042Sbrooks	gid_t gid;
512247042Sbrooks	uid_t uid;
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				}
588247042Sbrooks				if (gid_from_group(value, &gid) == 0)
589247042Sbrooks					st->st_gid = gid;
590223306Smarcel				else
591247042Sbrooks					error = EINVAL;
592223306Smarcel			} else
593223306Smarcel				error = ENOSYS;
594223306Smarcel			break;
595223306Smarcel		case 'l':
596223306Smarcel			if (strcmp(keyword, "link") == 0) {
597223306Smarcel				if (value == NULL) {
598223306Smarcel					error = ENOATTR;
599223306Smarcel					break;
600223306Smarcel				}
601223306Smarcel				node->symlink = strdup(value);
602223306Smarcel			} else
603223306Smarcel				error = ENOSYS;
604223306Smarcel			break;
605223306Smarcel		case 'm':
606223306Smarcel			if (strcmp(keyword, "mode") == 0) {
607223306Smarcel				if (value == NULL) {
608223306Smarcel					error = ENOATTR;
609223306Smarcel					break;
610223306Smarcel				}
611223306Smarcel				if (value[0] >= '0' && value[0] <= '9') {
612223306Smarcel					error = read_number(value, 8, &num,
613223306Smarcel					    0, 07777);
614223306Smarcel					if (!error) {
615223306Smarcel						st->st_mode &= S_IFMT;
616223306Smarcel						st->st_mode |= num;
617223306Smarcel					}
618223306Smarcel				} else {
619223306Smarcel					/* Symbolic mode not supported. */
620223306Smarcel					error = EINVAL;
621223306Smarcel					break;
622223306Smarcel				}
623223306Smarcel			} else
624223306Smarcel				error = ENOSYS;
625223306Smarcel			break;
626223306Smarcel		case 'o':
627223306Smarcel			if (strcmp(keyword, "optional") == 0) {
628223306Smarcel				if (value != NULL)
629223306Smarcel					error = ENXIO;
630223306Smarcel				node->flags |= FSNODE_F_OPTIONAL;
631223306Smarcel			} else
632223306Smarcel				error = ENOSYS;
633223306Smarcel			break;
634223306Smarcel		case 's':
635223306Smarcel			if (strcmp(keyword, "size") == 0) {
636223306Smarcel				if (value == NULL) {
637223306Smarcel					error = ENOATTR;
638223306Smarcel					break;
639223306Smarcel				}
640223306Smarcel				error = read_number(value, 10, &num,
641223306Smarcel				    0, INTMAX_MAX);
642223306Smarcel				if (!error)
643223306Smarcel					st->st_size = num;
644223306Smarcel			} else
645223306Smarcel				error = ENOSYS;
646223306Smarcel			break;
647223306Smarcel		case 't':
648223306Smarcel			if (strcmp(keyword, "time") == 0) {
649223306Smarcel				if (value == NULL) {
650223306Smarcel					error = ENOATTR;
651223306Smarcel					break;
652223306Smarcel				}
653223306Smarcel				p = strchr(value, '.');
654223306Smarcel				if (p != NULL)
655223306Smarcel					*p++ = '\0';
656223306Smarcel				error = read_number(value, 10, &num, 0,
657223306Smarcel				    INTMAX_MAX);
658223306Smarcel				if (error)
659223306Smarcel					break;
660223306Smarcel				st->st_atime = num;
661223306Smarcel				st->st_ctime = num;
662223306Smarcel				st->st_mtime = num;
663223306Smarcel				error = read_number(p, 10, &num, 0,
664223306Smarcel				    INTMAX_MAX);
665223306Smarcel				if (error)
666223306Smarcel					break;
667223306Smarcel				if (num != 0)
668223306Smarcel					error = EINVAL;
669223306Smarcel			} else if (strcmp(keyword, "type") == 0) {
670223306Smarcel				if (value == NULL) {
671223306Smarcel					error = ENOATTR;
672223306Smarcel					break;
673223306Smarcel				}
674223306Smarcel				if (strcmp(value, "dir") == 0)
675223306Smarcel					node->type = S_IFDIR;
676223306Smarcel				else if (strcmp(value, "file") == 0)
677223306Smarcel					node->type = S_IFREG;
678223306Smarcel				else if (strcmp(value, "link") == 0)
679223306Smarcel					node->type = S_IFLNK;
680223306Smarcel				else
681223306Smarcel					error = EINVAL;
682223306Smarcel			} else
683223306Smarcel				error = ENOSYS;
684223306Smarcel			break;
685223306Smarcel		case 'u':
686223306Smarcel			if (strcmp(keyword, "uid") == 0) {
687223306Smarcel				if (value == NULL) {
688223306Smarcel					error = ENOATTR;
689223306Smarcel					break;
690223306Smarcel				}
691223306Smarcel				error = read_number(value, 10, &num,
692223306Smarcel				    0, UINT_MAX);
693223306Smarcel				if (!error)
694223306Smarcel					st->st_uid = num;
695223306Smarcel			} else if (strcmp(keyword, "uname") == 0) {
696223306Smarcel				if (value == NULL) {
697223306Smarcel					error = ENOATTR;
698223306Smarcel					break;
699223306Smarcel				}
700247042Sbrooks				if (uid_from_user(value, &uid) == 0)
701247042Sbrooks					st->st_uid = uid;
702223306Smarcel				else
703247042Sbrooks					error = EINVAL;
704223306Smarcel			} else
705223306Smarcel				error = ENOSYS;
706223306Smarcel			break;
707223306Smarcel		default:
708223306Smarcel			error = ENOSYS;
709223306Smarcel			break;
710223306Smarcel		}
711223306Smarcel
712223306Smarcel		switch (error) {
713223306Smarcel		case EINVAL:
714223306Smarcel			mtree_error("%s: invalid value '%s'", keyword, value);
715223306Smarcel			break;
716223306Smarcel		case ENOATTR:
717223306Smarcel			mtree_error("%s: keyword needs a value", keyword);
718223306Smarcel			break;
719223306Smarcel		case ENOSYS:
720223306Smarcel			mtree_warning("%s: unsupported keyword", keyword);
721223306Smarcel			break;
722223306Smarcel		case ENXIO:
723223306Smarcel			mtree_error("%s: keyword does not take a value",
724223306Smarcel			    keyword);
725223306Smarcel			break;
726223306Smarcel		}
727223306Smarcel	} while (1);
728223306Smarcel
729223306Smarcel	if (error)
730223306Smarcel		return (error);
731223306Smarcel
732223306Smarcel	st->st_mode = (st->st_mode & ~S_IFMT) | node->type;
733223306Smarcel
734223306Smarcel	/* Nothing more to do for the global defaults. */
735223306Smarcel	if (node->name == NULL)
736223306Smarcel		return (0);
737223306Smarcel
738223306Smarcel	/*
739223306Smarcel	 * Be intelligent about the file type.
740223306Smarcel	 */
741223306Smarcel	if (node->contents != NULL) {
742223306Smarcel		if (node->symlink != NULL) {
743223306Smarcel			mtree_error("%s: both link and contents keywords "
744223306Smarcel			    "defined", node->name);
745223306Smarcel			return (0);
746223306Smarcel		}
747223306Smarcel		type = S_IFREG;
748242501Ssjg	} else if (node->type != 0) {
749242501Ssjg		type = node->type;
750242501Ssjg		if (type == S_IFREG) {
751242501Ssjg			/* the named path is the default contents */
752242501Ssjg			node->contents = mtree_file_path(node);
753242501Ssjg		}
754223306Smarcel	} else
755223306Smarcel		type = (node->symlink != NULL) ? S_IFLNK : S_IFDIR;
756223306Smarcel
757223306Smarcel	if (node->type == 0)
758223306Smarcel		node->type = type;
759223306Smarcel
760223306Smarcel	if (node->type != type) {
761223306Smarcel		mtree_error("%s: file type and defined keywords to not match",
762223306Smarcel		    node->name);
763223306Smarcel		return (0);
764223306Smarcel	}
765223306Smarcel
766223306Smarcel	st->st_mode = (st->st_mode & ~S_IFMT) | node->type;
767223306Smarcel
768223306Smarcel	if (node->contents == NULL)
769223306Smarcel		return (0);
770223306Smarcel
771223306Smarcel	name = mtree_resolve(node->contents, &istemp);
772223306Smarcel	if (name == NULL)
773223306Smarcel		return (errno);
774223306Smarcel
775223306Smarcel	if (stat(name, &sb) != 0) {
776223306Smarcel		mtree_error("%s: contents file '%s' not found", node->name,
777223306Smarcel		    name);
778223306Smarcel		free(name);
779223306Smarcel		return (0);
780223306Smarcel	}
781223306Smarcel
782247052Sbrooks	/*
783247052Sbrooks         * Check for hardlinks. If the contents key is used, then the check
784247052Sbrooks         * will only trigger if the contents file is a link even if it is used
785247052Sbrooks         * by more than one file
786247052Sbrooks	 */
787247052Sbrooks	if (sb.st_nlink > 1) {
788247052Sbrooks		fsinode *curino;
789247052Sbrooks
790247052Sbrooks		st->st_ino = sb.st_ino;
791247052Sbrooks		st->st_dev = sb.st_dev;
792247052Sbrooks		curino = link_check(node->inode);
793247052Sbrooks		if (curino != NULL) {
794247052Sbrooks			free(node->inode);
795247052Sbrooks			node->inode = curino;
796247052Sbrooks			node->inode->nlink++;
797247052Sbrooks		}
798247052Sbrooks	}
799247052Sbrooks
800223306Smarcel	free(node->contents);
801223306Smarcel	node->contents = name;
802223306Smarcel	st->st_size = sb.st_size;
803223306Smarcel	return (0);
804223306Smarcel}
805223306Smarcel
806223306Smarcelstatic int
807223306Smarcelread_mtree_command(FILE *fp)
808223306Smarcel{
809223306Smarcel	char cmd[10];
810223306Smarcel	int error;
811223306Smarcel
812223306Smarcel	error = read_word(fp, cmd, sizeof(cmd));
813223306Smarcel	if (error)
814223306Smarcel		goto out;
815223306Smarcel
816223306Smarcel	error = read_mtree_keywords(fp, &mtree_global);
817223306Smarcel
818223306Smarcel out:
819223306Smarcel	skip_to(fp, "\n");
820223306Smarcel	(void)getc(fp);
821223306Smarcel	return (error);
822223306Smarcel}
823223306Smarcel
824223306Smarcelstatic int
825223306Smarcelread_mtree_spec1(FILE *fp, bool def, const char *name)
826223306Smarcel{
827223306Smarcel	fsnode *last, *node, *parent;
828223306Smarcel	u_int type;
829223306Smarcel	int error;
830223306Smarcel
831223306Smarcel	assert(name[0] != '\0');
832223306Smarcel
833223306Smarcel	/*
834223306Smarcel	 * Treat '..' specially, because it only changes our current
835223306Smarcel	 * directory. We don't create a node for it. We simply ignore
836223306Smarcel	 * any keywords that may appear on the line as well.
837223306Smarcel	 * Going up a directory is a little non-obvious. A directory
838223306Smarcel	 * node has a corresponding '.' child. The parent of '.' is
839223306Smarcel	 * not the '.' node of the parent directory, but the directory
840223306Smarcel	 * node within the parent to which the child relates. However,
841223306Smarcel	 * going up a directory means we need to find the '.' node to
842223306Smarcel	 * which the directoy node is linked.  This we can do via the
843223306Smarcel	 * first * pointer, because '.' is always the first entry in a
844223306Smarcel	 * directory.
845223306Smarcel	 */
846223306Smarcel	if (IS_DOTDOT(name)) {
847223306Smarcel		/* This deals with NULL pointers as well. */
848223306Smarcel		if (mtree_current == mtree_root) {
849223306Smarcel			mtree_warning("ignoring .. in root directory");
850223306Smarcel			return (0);
851223306Smarcel		}
852223306Smarcel
853223306Smarcel		node = mtree_current;
854223306Smarcel
855223306Smarcel		assert(node != NULL);
856223306Smarcel		assert(IS_DOT(node->name));
857223306Smarcel		assert(node->first == node);
858223306Smarcel
859223306Smarcel		/* Get the corresponding directory node in the parent. */
860223306Smarcel		node = mtree_current->parent;
861223306Smarcel
862223306Smarcel		assert(node != NULL);
863223306Smarcel		assert(!IS_DOT(node->name));
864223306Smarcel
865223306Smarcel		node = node->first;
866223306Smarcel
867223306Smarcel		assert(node != NULL);
868223306Smarcel		assert(IS_DOT(node->name));
869223306Smarcel		assert(node->first == node);
870223306Smarcel
871223306Smarcel		mtree_current = node;
872223306Smarcel		return (0);
873223306Smarcel	}
874223306Smarcel
875223306Smarcel	/*
876223306Smarcel	 * If we don't have a current directory and the first specification
877223306Smarcel	 * (either implicit or defined) is not '.', then we need to create
878223306Smarcel	 * a '.' node first (using a recursive call).
879223306Smarcel	 */
880223306Smarcel	if (!IS_DOT(name) && mtree_current == NULL) {
881223306Smarcel		error = read_mtree_spec1(fp, false, ".");
882223306Smarcel		if (error)
883223306Smarcel			return (error);
884223306Smarcel	}
885223306Smarcel
886223306Smarcel	/*
887223306Smarcel	 * Lookup the name in the current directory (if we have a current
888223306Smarcel	 * directory) to make sure we do not create multiple nodes for the
889223306Smarcel	 * same component. For non-definitions, if we find a node with the
890223306Smarcel	 * same name, simply change the current directory. For definitions
891223306Smarcel	 * more happens.
892223306Smarcel	 */
893223306Smarcel	last = NULL;
894223306Smarcel	node = mtree_current;
895223306Smarcel	while (node != NULL) {
896223306Smarcel		assert(node->first == mtree_current);
897223306Smarcel
898223306Smarcel		if (strcmp(name, node->name) == 0) {
899223306Smarcel			if (def == true) {
900247041Sbrooks				if (!dupsok)
901247041Sbrooks					mtree_error(
902247041Sbrooks					    "duplicate definition of %s",
903247041Sbrooks					    name);
904247041Sbrooks				else
905247041Sbrooks					mtree_warning(
906247041Sbrooks					    "duplicate definition of %s",
907247041Sbrooks					    name);
908223306Smarcel				return (0);
909223306Smarcel			}
910223306Smarcel
911223306Smarcel			if (node->type != S_IFDIR) {
912223306Smarcel				mtree_error("%s is not a directory", name);
913223306Smarcel				return (0);
914223306Smarcel			}
915223306Smarcel
916223306Smarcel			assert(!IS_DOT(name));
917223306Smarcel
918223306Smarcel			node = node->child;
919223306Smarcel
920223306Smarcel			assert(node != NULL);
921223306Smarcel			assert(IS_DOT(node->name));
922223306Smarcel
923223306Smarcel			mtree_current = node;
924223306Smarcel			return (0);
925223306Smarcel		}
926223306Smarcel
927223306Smarcel		last = node;
928223306Smarcel		node = last->next;
929223306Smarcel	}
930223306Smarcel
931223306Smarcel	parent = (mtree_current != NULL) ? mtree_current->parent : NULL;
932223306Smarcel	type = (def == false || IS_DOT(name)) ? S_IFDIR : 0;
933223306Smarcel	node = create_node(name, type, parent, &mtree_global);
934223306Smarcel	if (node == NULL)
935223306Smarcel		return (ENOMEM);
936223306Smarcel
937223306Smarcel	if (def == true) {
938223306Smarcel		error = read_mtree_keywords(fp, node);
939223306Smarcel		if (error) {
940223306Smarcel			destroy_node(node);
941223306Smarcel			return (error);
942223306Smarcel		}
943223306Smarcel	}
944223306Smarcel
945223306Smarcel	node->first = (mtree_current != NULL) ? mtree_current : node;
946223306Smarcel
947223306Smarcel	if (last != NULL)
948223306Smarcel		last->next = node;
949223306Smarcel
950223306Smarcel	if (node->type != S_IFDIR)
951223306Smarcel		return (0);
952223306Smarcel
953223306Smarcel	if (!IS_DOT(node->name)) {
954223306Smarcel		parent = node;
955223306Smarcel		node = create_node(".", S_IFDIR, parent, parent);
956223306Smarcel		if (node == NULL) {
957223306Smarcel			last->next = NULL;
958223306Smarcel			destroy_node(parent);
959223306Smarcel			return (ENOMEM);
960223306Smarcel		}
961223306Smarcel		parent->child = node;
962223306Smarcel		node->first = node;
963223306Smarcel	}
964223306Smarcel
965223306Smarcel	assert(node != NULL);
966223306Smarcel	assert(IS_DOT(node->name));
967223306Smarcel	assert(node->first == node);
968223306Smarcel
969223306Smarcel	mtree_current = node;
970223306Smarcel	if (mtree_root == NULL)
971223306Smarcel		mtree_root = node;
972223306Smarcel
973223306Smarcel	return (0);
974223306Smarcel}
975223306Smarcel
976223306Smarcelstatic int
977223306Smarcelread_mtree_spec(FILE *fp)
978223306Smarcel{
979223306Smarcel	char pathspec[PATH_MAX];
980223306Smarcel	char *cp;
981223306Smarcel	int error;
982223306Smarcel
983223306Smarcel	error = read_word(fp, pathspec, sizeof(pathspec));
984223306Smarcel	if (error)
985223306Smarcel		goto out;
986223306Smarcel
987223306Smarcel	cp = strchr(pathspec, '/');
988223306Smarcel	if (cp != NULL) {
989223306Smarcel		/* Absolute pathname */
990223306Smarcel		mtree_current = mtree_root;
991223306Smarcel
992223306Smarcel		do {
993223306Smarcel			*cp++ = '\0';
994223306Smarcel
995247043Sbrooks			/* Disallow '..' as a component. */
996247043Sbrooks			if (IS_DOTDOT(pathspec)) {
997247043Sbrooks				mtree_error("absolute path cannot contain "
998247043Sbrooks				    ".. component");
999223306Smarcel				goto out;
1000223306Smarcel			}
1001223306Smarcel
1002247043Sbrooks			/* Ignore multiple adjacent slashes and '.'. */
1003247043Sbrooks			if (pathspec[0] != '\0' && !IS_DOT(pathspec))
1004223306Smarcel				error = read_mtree_spec1(fp, false, pathspec);
1005223306Smarcel			memmove(pathspec, cp, strlen(cp) + 1);
1006223306Smarcel			cp = strchr(pathspec, '/');
1007223306Smarcel		} while (!error && cp != NULL);
1008223306Smarcel
1009223306Smarcel		/* Disallow '.' and '..' as the last component. */
1010223306Smarcel		if (!error && (IS_DOT(pathspec) || IS_DOTDOT(pathspec))) {
1011223306Smarcel			mtree_error("absolute path cannot contain . or .. "
1012223306Smarcel			    "components");
1013223306Smarcel			goto out;
1014223306Smarcel		}
1015223306Smarcel	}
1016223306Smarcel
1017223306Smarcel	/* Ignore absolute specfications that end with a slash. */
1018223306Smarcel	if (!error && pathspec[0] != '\0')
1019223306Smarcel		error = read_mtree_spec1(fp, true, pathspec);
1020223306Smarcel
1021223306Smarcel out:
1022223306Smarcel	skip_to(fp, "\n");
1023223306Smarcel	(void)getc(fp);
1024223306Smarcel	return (error);
1025223306Smarcel}
1026223306Smarcel
1027223306Smarcelfsnode *
1028223306Smarcelread_mtree(const char *fname, fsnode *node)
1029223306Smarcel{
1030223306Smarcel	struct mtree_fileinfo *fi;
1031223306Smarcel	FILE *fp;
1032223306Smarcel	int c, error;
1033223306Smarcel
1034223306Smarcel	/* We do not yet support nesting... */
1035223306Smarcel	assert(node == NULL);
1036223306Smarcel
1037223306Smarcel	if (strcmp(fname, "-") == 0)
1038223306Smarcel		fp = stdin;
1039223306Smarcel	else {
1040223306Smarcel		fp = fopen(fname, "r");
1041223306Smarcel		if (fp == NULL)
1042223306Smarcel			err(1, "Can't open `%s'", fname);
1043223306Smarcel	}
1044223306Smarcel
1045223306Smarcel	error = mtree_file_push(fname, fp);
1046223306Smarcel	if (error)
1047223306Smarcel		goto out;
1048223306Smarcel
1049223306Smarcel	bzero(&mtree_global, sizeof(mtree_global));
1050223306Smarcel	bzero(&mtree_global_inode, sizeof(mtree_global_inode));
1051223306Smarcel	mtree_global.inode = &mtree_global_inode;
1052223306Smarcel	mtree_global_inode.nlink = 1;
1053250605Smarcel	mtree_global_inode.st.st_nlink = 1;
1054223306Smarcel	mtree_global_inode.st.st_atime = mtree_global_inode.st.st_ctime =
1055223306Smarcel	    mtree_global_inode.st.st_mtime = time(NULL);
1056223306Smarcel	errors = warnings = 0;
1057223306Smarcel
1058223306Smarcel	setgroupent(1);
1059223306Smarcel	setpassent(1);
1060223306Smarcel
1061223306Smarcel	mtree_root = node;
1062223306Smarcel	mtree_current = node;
1063223306Smarcel	do {
1064223306Smarcel		/* Start of a new line... */
1065223306Smarcel		fi = SLIST_FIRST(&mtree_fileinfo);
1066223306Smarcel		fi->line++;
1067223306Smarcel
1068223306Smarcel		error = skip_over(fp, " \t");
1069223306Smarcel		if (error)
1070223306Smarcel			break;
1071223306Smarcel
1072223306Smarcel		c = getc(fp);
1073223306Smarcel		if (c == EOF) {
1074223306Smarcel			error = ferror(fp) ? errno : -1;
1075223306Smarcel			break;
1076223306Smarcel		}
1077223306Smarcel
1078223306Smarcel		switch (c) {
1079223306Smarcel		case '\n':		/* empty line */
1080223306Smarcel			error = 0;
1081223306Smarcel			break;
1082223306Smarcel		case '#':		/* comment -- skip to end of line. */
1083223306Smarcel			error = skip_to(fp, "\n");
1084223306Smarcel			if (!error)
1085223306Smarcel				(void)getc(fp);
1086223306Smarcel			break;
1087223306Smarcel		case '/':		/* special commands */
1088223306Smarcel			error = read_mtree_command(fp);
1089223306Smarcel			break;
1090223306Smarcel		default:		/* specification */
1091223306Smarcel			ungetc(c, fp);
1092223306Smarcel			error = read_mtree_spec(fp);
1093223306Smarcel			break;
1094223306Smarcel		}
1095223306Smarcel	} while (!error);
1096223306Smarcel
1097223306Smarcel	endpwent();
1098223306Smarcel	endgrent();
1099223306Smarcel
1100223306Smarcel	if (error <= 0 && (errors || warnings)) {
1101223306Smarcel		warnx("%u error(s) and %u warning(s) in mtree manifest",
1102223306Smarcel		    errors, warnings);
1103223306Smarcel		if (errors)
1104223306Smarcel			exit(1);
1105223306Smarcel	}
1106223306Smarcel
1107223306Smarcel out:
1108223306Smarcel	if (error > 0)
1109223306Smarcel		errc(1, error, "Error reading mtree file");
1110223306Smarcel
1111223306Smarcel	if (fp != stdin)
1112223306Smarcel		fclose(fp);
1113223306Smarcel
1114223306Smarcel	if (mtree_root != NULL)
1115223306Smarcel		return (mtree_root);
1116223306Smarcel
1117223306Smarcel	/* Handle empty specifications. */
1118223306Smarcel	node = create_node(".", S_IFDIR, NULL, &mtree_global);
1119223306Smarcel	node->first = node;
1120223306Smarcel	return (node);
1121223306Smarcel}
1122