mtree.c revision 247041
1/*-
2 * Copyright (c) 2011 Marcel Moolenaar
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include <sys/cdefs.h>
27__FBSDID("$FreeBSD: head/usr.sbin/makefs/mtree.c 247041 2013-02-20 15:18:42Z brooks $");
28
29#include <sys/param.h>
30#include <sys/queue.h>
31#include <sys/sbuf.h>
32#include <sys/stat.h>
33#include <sys/types.h>
34#include <assert.h>
35#include <errno.h>
36#include <fcntl.h>
37#include <grp.h>
38#include <inttypes.h>
39#include <pwd.h>
40#include <stdarg.h>
41#include <stdbool.h>
42#include <stddef.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46#include <strings.h>
47#include <unistd.h>
48
49#include "makefs.h"
50
51#define	IS_DOT(nm)	((nm)[0] == '.' && (nm)[1] == '\0')
52#define	IS_DOTDOT(nm)	((nm)[0] == '.' && (nm)[1] == '.' && (nm)[2] == '\0')
53
54struct mtree_fileinfo {
55	SLIST_ENTRY(mtree_fileinfo) next;
56	FILE *fp;
57	const char *name;
58	u_int line;
59};
60
61/* Global state used while parsing. */
62static SLIST_HEAD(, mtree_fileinfo) mtree_fileinfo =
63    SLIST_HEAD_INITIALIZER(mtree_fileinfo);
64static fsnode *mtree_root;
65static fsnode *mtree_current;
66static fsnode mtree_global;
67static fsinode mtree_global_inode;
68static u_int errors, warnings;
69
70static void mtree_error(const char *, ...) __printflike(1, 2);
71static void mtree_warning(const char *, ...) __printflike(1, 2);
72
73static int
74mtree_file_push(const char *name, FILE *fp)
75{
76	struct mtree_fileinfo *fi;
77
78	fi = malloc(sizeof(*fi));
79	if (fi == NULL)
80		return (ENOMEM);
81
82	if (strcmp(name, "-") == 0)
83		fi->name = strdup("(stdin)");
84	else
85		fi->name = strdup(name);
86	if (fi->name == NULL) {
87		free(fi);
88		return (ENOMEM);
89	}
90
91	fi->fp = fp;
92	fi->line = 0;
93
94	SLIST_INSERT_HEAD(&mtree_fileinfo, fi, next);
95	return (0);
96}
97
98static void
99mtree_print(const char *msgtype, const char *fmt, va_list ap)
100{
101	struct mtree_fileinfo *fi;
102
103	if (msgtype != NULL) {
104		fi = SLIST_FIRST(&mtree_fileinfo);
105		if (fi != NULL)
106			fprintf(stderr, "%s:%u: ", fi->name, fi->line);
107		fprintf(stderr, "%s: ", msgtype);
108	}
109	vfprintf(stderr, fmt, ap);
110}
111
112static void
113mtree_error(const char *fmt, ...)
114{
115	va_list ap;
116
117	va_start(ap, fmt);
118	mtree_print("error", fmt, ap);
119	va_end(ap);
120
121	errors++;
122	fputc('\n', stderr);
123}
124
125static void
126mtree_warning(const char *fmt, ...)
127{
128	va_list ap;
129
130	va_start(ap, fmt);
131	mtree_print("warning", fmt, ap);
132	va_end(ap);
133
134	warnings++;
135	fputc('\n', stderr);
136}
137
138#ifndef MAKEFS_MAX_TREE_DEPTH
139# define MAKEFS_MAX_TREE_DEPTH (MAXPATHLEN/2)
140#endif
141
142/* construct path to node->name */
143static char *
144mtree_file_path(fsnode *node)
145{
146	fsnode *pnode;
147	struct sbuf *sb;
148	char *res, *rp[MAKEFS_MAX_TREE_DEPTH];
149	int depth;
150
151	depth = 0;
152	rp[depth] = node->name;
153	for (pnode = node->parent; pnode && depth < MAKEFS_MAX_TREE_DEPTH;
154	     pnode = pnode->parent) {
155		if (strcmp(pnode->name, ".") == 0)
156			break;
157		rp[++depth] = pnode->name;
158	}
159
160	sb = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND);
161	if (sb == NULL) {
162		errno = ENOMEM;
163		return (NULL);
164	}
165	while (depth > 0) {
166		sbuf_cat(sb, rp[depth--]);
167		sbuf_putc(sb, '/');
168	}
169	sbuf_cat(sb, rp[depth]);
170	sbuf_finish(sb);
171	res = strdup(sbuf_data(sb));
172	sbuf_delete(sb);
173	if (res == NULL)
174		errno = ENOMEM;
175	return res;
176
177}
178
179/* mtree_resolve() sets errno to indicate why NULL was returned. */
180static char *
181mtree_resolve(const char *spec, int *istemp)
182{
183	struct sbuf *sb;
184	char *res, *var;
185	const char *base, *p, *v;
186	size_t len;
187	int c, error, quoted, subst;
188
189	len = strlen(spec);
190	if (len == 0) {
191		errno = EINVAL;
192		return (NULL);
193	}
194
195	c = (len > 1) ? (spec[0] == spec[len - 1]) ? spec[0] : 0 : 0;
196	*istemp = (c == '`') ? 1 : 0;
197	subst = (c == '`' || c == '"') ? 1 : 0;
198	quoted = (subst || c == '\'') ? 1 : 0;
199
200	if (!subst) {
201		res = strdup(spec + quoted);
202		if (res != NULL && quoted)
203			res[len - 2] = '\0';
204		return (res);
205	}
206
207	sb = sbuf_new_auto();
208	if (sb == NULL) {
209		errno = ENOMEM;
210		return (NULL);
211	}
212
213	base = spec + 1;
214	len -= 2;
215	error = 0;
216	while (len > 0) {
217		p = strchr(base, '$');
218		if (p == NULL) {
219			sbuf_bcat(sb, base, len);
220			base += len;
221			len = 0;
222			continue;
223		}
224		/* The following is safe. spec always starts with a quote. */
225		if (p[-1] == '\\')
226			p--;
227		if (base != p) {
228			sbuf_bcat(sb, base, p - base);
229			len -= p - base;
230			base = p;
231		}
232		if (*p == '\\') {
233			sbuf_putc(sb, '$');
234			base += 2;
235			len -= 2;
236			continue;
237		}
238		/* Skip the '$'. */
239		base++;
240		len--;
241		/* Handle ${X} vs $X. */
242		v = base;
243		if (*base == '{') {
244			p = strchr(v, '}');
245			if (p == NULL)
246				p = v;
247		} else
248			p = v;
249		len -= (p + 1) - base;
250		base = p + 1;
251
252		if (v == p) {
253			sbuf_putc(sb, *v);
254			continue;
255		}
256
257		error = ENOMEM;
258		var = calloc(p - v, 1);
259		if (var == NULL)
260			break;
261
262		memcpy(var, v + 1, p - v - 1);
263		if (strcmp(var, ".CURDIR") == 0) {
264			res = getcwd(NULL, 0);
265			if (res == NULL)
266				break;
267		} else if (strcmp(var, ".PROG") == 0) {
268			res = strdup(getprogname());
269			if (res == NULL)
270				break;
271		} else {
272			v = getenv(var);
273			if (v != NULL) {
274				res = strdup(v);
275				if (res == NULL)
276					break;
277			} else
278				res = NULL;
279		}
280		error = 0;
281
282		if (res != NULL) {
283			sbuf_cat(sb, res);
284			free(res);
285		}
286		free(var);
287	}
288
289	sbuf_finish(sb);
290	res = (error == 0) ? strdup(sbuf_data(sb)) : NULL;
291	sbuf_delete(sb);
292	if (res == NULL)
293		errno = ENOMEM;
294	return (res);
295}
296
297static int
298skip_over(FILE *fp, const char *cs)
299{
300	int c;
301
302	c = getc(fp);
303	while (c != EOF && strchr(cs, c) != NULL)
304		c = getc(fp);
305	if (c != EOF) {
306		ungetc(c, fp);
307		return (0);
308	}
309	return (ferror(fp) ? errno : -1);
310}
311
312static int
313skip_to(FILE *fp, const char *cs)
314{
315	int c;
316
317	c = getc(fp);
318	while (c != EOF && strchr(cs, c) == NULL)
319		c = getc(fp);
320	if (c != EOF) {
321		ungetc(c, fp);
322		return (0);
323	}
324	return (ferror(fp) ? errno : -1);
325}
326
327static int
328read_word(FILE *fp, char *buf, size_t bufsz)
329{
330	struct mtree_fileinfo *fi;
331	size_t idx, qidx;
332	int c, done, error, esc, qlvl;
333
334	if (bufsz == 0)
335		return (EINVAL);
336
337	done = 0;
338	esc = 0;
339	idx = 0;
340	qidx = -1;
341	qlvl = 0;
342	do {
343		c = getc(fp);
344		switch (c) {
345		case EOF:
346			buf[idx] = '\0';
347			error = ferror(fp) ? errno : -1;
348			if (error == -1)
349				mtree_error("unexpected end of file");
350			return (error);
351		case '\\':
352			esc++;
353			if (esc == 1)
354				continue;
355			break;
356		case '`':
357		case '\'':
358		case '"':
359			if (esc)
360				break;
361			if (qlvl == 0) {
362				qlvl++;
363				qidx = idx;
364			} else if (c == buf[qidx]) {
365				qlvl--;
366				if (qlvl > 0) {
367					do {
368						qidx--;
369					} while (buf[qidx] != '`' &&
370					    buf[qidx] != '\'' &&
371					    buf[qidx] != '"');
372				} else
373					qidx = -1;
374			} else {
375				qlvl++;
376				qidx = idx;
377			}
378			break;
379		case ' ':
380		case '\t':
381		case '\n':
382			if (!esc && qlvl == 0) {
383				ungetc(c, fp);
384				c = '\0';
385				done = 1;
386				break;
387			}
388			if (c == '\n') {
389				/*
390				 * We going to eat the newline ourselves.
391				 */
392				if (qlvl > 0)
393					mtree_warning("quoted word straddles "
394					    "onto next line.");
395				fi = SLIST_FIRST(&mtree_fileinfo);
396				fi->line++;
397			}
398			break;
399		case 'a':
400			if (esc)
401				c = '\a';
402			break;
403		case 'b':
404			if (esc)
405				c = '\b';
406			break;
407		case 'f':
408			if (esc)
409				c = '\f';
410			break;
411		case 'n':
412			if (esc)
413				c = '\n';
414			break;
415		case 'r':
416			if (esc)
417				c = '\r';
418			break;
419		case 't':
420			if (esc)
421				c = '\t';
422			break;
423		case 'v':
424			if (esc)
425				c = '\v';
426			break;
427		}
428		buf[idx++] = c;
429		esc = 0;
430	} while (idx < bufsz && !done);
431
432	if (idx >= bufsz) {
433		mtree_error("word too long to fit buffer (max %zu characters)",
434		    bufsz);
435		skip_to(fp, " \t\n");
436	}
437	return (0);
438}
439
440static fsnode *
441create_node(const char *name, u_int type, fsnode *parent, fsnode *global)
442{
443	fsnode *n;
444
445	n = calloc(1, sizeof(*n));
446	if (n == NULL)
447		return (NULL);
448
449	n->name = strdup(name);
450	if (n->name == NULL) {
451		free(n);
452		return (NULL);
453	}
454
455	n->type = (type == 0) ? global->type : type;
456	n->parent = parent;
457
458	n->inode = calloc(1, sizeof(*n->inode));
459	if (n->inode == NULL) {
460		free(n->name);
461		free(n);
462		return (NULL);
463	}
464
465	/* Assign global options/defaults. */
466	bcopy(global->inode, n->inode, sizeof(*n->inode));
467	n->inode->st.st_mode = (n->inode->st.st_mode & ~S_IFMT) | n->type;
468
469	if (n->type == S_IFLNK)
470		n->symlink = global->symlink;
471	else if (n->type == S_IFREG)
472		n->contents = global->contents;
473
474	return (n);
475}
476
477static void
478destroy_node(fsnode *n)
479{
480
481	assert(n != NULL);
482	assert(n->name != NULL);
483	assert(n->inode != NULL);
484
485	free(n->inode);
486	free(n->name);
487	free(n);
488}
489
490static int
491read_number(const char *tok, u_int base, intmax_t *res, intmax_t min,
492    intmax_t max)
493{
494	char *end;
495	intmax_t val;
496
497	val = strtoimax(tok, &end, base);
498	if (end == tok || end[0] != '\0')
499		return (EINVAL);
500	if (val < min || val > max)
501		return (EDOM);
502	*res = val;
503	return (0);
504}
505
506static int
507read_mtree_keywords(FILE *fp, fsnode *node)
508{
509	char keyword[PATH_MAX];
510	char *name, *p, *value;
511	struct group *grent;
512	struct passwd *pwent;
513	struct stat *st, sb;
514	intmax_t num;
515	u_long flset, flclr;
516	int error, istemp, type;
517
518	st = &node->inode->st;
519	do {
520		error = skip_over(fp, " \t");
521		if (error)
522			break;
523
524		error = read_word(fp, keyword, sizeof(keyword));
525		if (error)
526			break;
527
528		if (keyword[0] == '\0')
529			break;
530
531		value = strchr(keyword, '=');
532		if (value != NULL)
533			*value++ = '\0';
534
535		/*
536		 * We use EINVAL, ENOATTR, ENOSYS and ENXIO to signal
537		 * certain conditions:
538		 *   EINVAL -	Value provided for a keyword that does
539		 *		not take a value. The value is ignored.
540		 *   ENOATTR -	Value missing for a keyword that needs
541		 *		a value. The keyword is ignored.
542		 *   ENOSYS -	Unsupported keyword encountered. The
543		 *		keyword is ignored.
544		 *   ENXIO -	Value provided for a keyword that does
545		 *		not take a value. The value is ignored.
546		 */
547		switch (keyword[0]) {
548		case 'c':
549			if (strcmp(keyword, "contents") == 0) {
550				if (value == NULL) {
551					error = ENOATTR;
552					break;
553				}
554				node->contents = strdup(value);
555			} else
556				error = ENOSYS;
557			break;
558		case 'f':
559			if (strcmp(keyword, "flags") == 0) {
560				if (value == NULL) {
561					error = ENOATTR;
562					break;
563				}
564				flset = flclr = 0;
565				if (!strtofflags(&value, &flset, &flclr)) {
566					st->st_flags &= ~flclr;
567					st->st_flags |= flset;
568				} else
569					error = errno;
570			} else
571				error = ENOSYS;
572			break;
573		case 'g':
574			if (strcmp(keyword, "gid") == 0) {
575				if (value == NULL) {
576					error = ENOATTR;
577					break;
578				}
579				error = read_number(value, 10, &num,
580				    0, UINT_MAX);
581				if (!error)
582					st->st_gid = num;
583			} else if (strcmp(keyword, "gname") == 0) {
584				if (value == NULL) {
585					error = ENOATTR;
586					break;
587				}
588				grent = getgrnam(value);
589				if (grent != NULL)
590					st->st_gid = grent->gr_gid;
591				else
592					error = errno;
593			} else
594				error = ENOSYS;
595			break;
596		case 'l':
597			if (strcmp(keyword, "link") == 0) {
598				if (value == NULL) {
599					error = ENOATTR;
600					break;
601				}
602				node->symlink = strdup(value);
603			} else
604				error = ENOSYS;
605			break;
606		case 'm':
607			if (strcmp(keyword, "mode") == 0) {
608				if (value == NULL) {
609					error = ENOATTR;
610					break;
611				}
612				if (value[0] >= '0' && value[0] <= '9') {
613					error = read_number(value, 8, &num,
614					    0, 07777);
615					if (!error) {
616						st->st_mode &= S_IFMT;
617						st->st_mode |= num;
618					}
619				} else {
620					/* Symbolic mode not supported. */
621					error = EINVAL;
622					break;
623				}
624			} else
625				error = ENOSYS;
626			break;
627		case 'o':
628			if (strcmp(keyword, "optional") == 0) {
629				if (value != NULL)
630					error = ENXIO;
631				node->flags |= FSNODE_F_OPTIONAL;
632			} else
633				error = ENOSYS;
634			break;
635		case 's':
636			if (strcmp(keyword, "size") == 0) {
637				if (value == NULL) {
638					error = ENOATTR;
639					break;
640				}
641				error = read_number(value, 10, &num,
642				    0, INTMAX_MAX);
643				if (!error)
644					st->st_size = num;
645			} else
646				error = ENOSYS;
647			break;
648		case 't':
649			if (strcmp(keyword, "time") == 0) {
650				if (value == NULL) {
651					error = ENOATTR;
652					break;
653				}
654				p = strchr(value, '.');
655				if (p != NULL)
656					*p++ = '\0';
657				error = read_number(value, 10, &num, 0,
658				    INTMAX_MAX);
659				if (error)
660					break;
661				st->st_atime = num;
662				st->st_ctime = num;
663				st->st_mtime = num;
664				error = read_number(p, 10, &num, 0,
665				    INTMAX_MAX);
666				if (error)
667					break;
668				if (num != 0)
669					error = EINVAL;
670			} else if (strcmp(keyword, "type") == 0) {
671				if (value == NULL) {
672					error = ENOATTR;
673					break;
674				}
675				if (strcmp(value, "dir") == 0)
676					node->type = S_IFDIR;
677				else if (strcmp(value, "file") == 0)
678					node->type = S_IFREG;
679				else if (strcmp(value, "link") == 0)
680					node->type = S_IFLNK;
681				else
682					error = EINVAL;
683			} else
684				error = ENOSYS;
685			break;
686		case 'u':
687			if (strcmp(keyword, "uid") == 0) {
688				if (value == NULL) {
689					error = ENOATTR;
690					break;
691				}
692				error = read_number(value, 10, &num,
693				    0, UINT_MAX);
694				if (!error)
695					st->st_uid = num;
696			} else if (strcmp(keyword, "uname") == 0) {
697				if (value == NULL) {
698					error = ENOATTR;
699					break;
700				}
701				pwent = getpwnam(value);
702				if (pwent != NULL)
703					st->st_uid = pwent->pw_uid;
704				else
705					error = errno;
706			} else
707				error = ENOSYS;
708			break;
709		default:
710			error = ENOSYS;
711			break;
712		}
713
714		switch (error) {
715		case EINVAL:
716			mtree_error("%s: invalid value '%s'", keyword, value);
717			break;
718		case ENOATTR:
719			mtree_error("%s: keyword needs a value", keyword);
720			break;
721		case ENOSYS:
722			mtree_warning("%s: unsupported keyword", keyword);
723			break;
724		case ENXIO:
725			mtree_error("%s: keyword does not take a value",
726			    keyword);
727			break;
728		}
729	} while (1);
730
731	if (error)
732		return (error);
733
734	st->st_mode = (st->st_mode & ~S_IFMT) | node->type;
735
736	/* Nothing more to do for the global defaults. */
737	if (node->name == NULL)
738		return (0);
739
740	/*
741	 * Be intelligent about the file type.
742	 */
743	if (node->contents != NULL) {
744		if (node->symlink != NULL) {
745			mtree_error("%s: both link and contents keywords "
746			    "defined", node->name);
747			return (0);
748		}
749		type = S_IFREG;
750	} else if (node->type != 0) {
751		type = node->type;
752		if (type == S_IFREG) {
753			/* the named path is the default contents */
754			node->contents = mtree_file_path(node);
755		}
756	} else
757		type = (node->symlink != NULL) ? S_IFLNK : S_IFDIR;
758
759	if (node->type == 0)
760		node->type = type;
761
762	if (node->type != type) {
763		mtree_error("%s: file type and defined keywords to not match",
764		    node->name);
765		return (0);
766	}
767
768	st->st_mode = (st->st_mode & ~S_IFMT) | node->type;
769
770	if (node->contents == NULL)
771		return (0);
772
773	name = mtree_resolve(node->contents, &istemp);
774	if (name == NULL)
775		return (errno);
776
777	if (stat(name, &sb) != 0) {
778		mtree_error("%s: contents file '%s' not found", node->name,
779		    name);
780		free(name);
781		return (0);
782	}
783
784	free(node->contents);
785	node->contents = name;
786	st->st_size = sb.st_size;
787	return (0);
788}
789
790static int
791read_mtree_command(FILE *fp)
792{
793	char cmd[10];
794	int error;
795
796	error = read_word(fp, cmd, sizeof(cmd));
797	if (error)
798		goto out;
799
800	error = read_mtree_keywords(fp, &mtree_global);
801
802 out:
803	skip_to(fp, "\n");
804	(void)getc(fp);
805	return (error);
806}
807
808static int
809read_mtree_spec1(FILE *fp, bool def, const char *name)
810{
811	fsnode *last, *node, *parent;
812	u_int type;
813	int error;
814
815	assert(name[0] != '\0');
816
817	/*
818	 * Treat '..' specially, because it only changes our current
819	 * directory. We don't create a node for it. We simply ignore
820	 * any keywords that may appear on the line as well.
821	 * Going up a directory is a little non-obvious. A directory
822	 * node has a corresponding '.' child. The parent of '.' is
823	 * not the '.' node of the parent directory, but the directory
824	 * node within the parent to which the child relates. However,
825	 * going up a directory means we need to find the '.' node to
826	 * which the directoy node is linked.  This we can do via the
827	 * first * pointer, because '.' is always the first entry in a
828	 * directory.
829	 */
830	if (IS_DOTDOT(name)) {
831		/* This deals with NULL pointers as well. */
832		if (mtree_current == mtree_root) {
833			mtree_warning("ignoring .. in root directory");
834			return (0);
835		}
836
837		node = mtree_current;
838
839		assert(node != NULL);
840		assert(IS_DOT(node->name));
841		assert(node->first == node);
842
843		/* Get the corresponding directory node in the parent. */
844		node = mtree_current->parent;
845
846		assert(node != NULL);
847		assert(!IS_DOT(node->name));
848
849		node = node->first;
850
851		assert(node != NULL);
852		assert(IS_DOT(node->name));
853		assert(node->first == node);
854
855		mtree_current = node;
856		return (0);
857	}
858
859	/*
860	 * If we don't have a current directory and the first specification
861	 * (either implicit or defined) is not '.', then we need to create
862	 * a '.' node first (using a recursive call).
863	 */
864	if (!IS_DOT(name) && mtree_current == NULL) {
865		error = read_mtree_spec1(fp, false, ".");
866		if (error)
867			return (error);
868	}
869
870	/*
871	 * Lookup the name in the current directory (if we have a current
872	 * directory) to make sure we do not create multiple nodes for the
873	 * same component. For non-definitions, if we find a node with the
874	 * same name, simply change the current directory. For definitions
875	 * more happens.
876	 */
877	last = NULL;
878	node = mtree_current;
879	while (node != NULL) {
880		assert(node->first == mtree_current);
881
882		if (strcmp(name, node->name) == 0) {
883			if (def == true) {
884				if (!dupsok)
885					mtree_error(
886					    "duplicate definition of %s",
887					    name);
888				else
889					mtree_warning(
890					    "duplicate definition of %s",
891					    name);
892				return (0);
893			}
894
895			if (node->type != S_IFDIR) {
896				mtree_error("%s is not a directory", name);
897				return (0);
898			}
899
900			assert(!IS_DOT(name));
901
902			node = node->child;
903
904			assert(node != NULL);
905			assert(IS_DOT(node->name));
906
907			mtree_current = node;
908			return (0);
909		}
910
911		last = node;
912		node = last->next;
913	}
914
915	parent = (mtree_current != NULL) ? mtree_current->parent : NULL;
916	type = (def == false || IS_DOT(name)) ? S_IFDIR : 0;
917	node = create_node(name, type, parent, &mtree_global);
918	if (node == NULL)
919		return (ENOMEM);
920
921	if (def == true) {
922		error = read_mtree_keywords(fp, node);
923		if (error) {
924			destroy_node(node);
925			return (error);
926		}
927	}
928
929	node->first = (mtree_current != NULL) ? mtree_current : node;
930
931	if (last != NULL)
932		last->next = node;
933
934	if (node->type != S_IFDIR)
935		return (0);
936
937	if (!IS_DOT(node->name)) {
938		parent = node;
939		node = create_node(".", S_IFDIR, parent, parent);
940		if (node == NULL) {
941			last->next = NULL;
942			destroy_node(parent);
943			return (ENOMEM);
944		}
945		parent->child = node;
946		node->first = node;
947	}
948
949	assert(node != NULL);
950	assert(IS_DOT(node->name));
951	assert(node->first == node);
952
953	mtree_current = node;
954	if (mtree_root == NULL)
955		mtree_root = node;
956
957	return (0);
958}
959
960static int
961read_mtree_spec(FILE *fp)
962{
963	char pathspec[PATH_MAX];
964	char *cp;
965	int error;
966
967	error = read_word(fp, pathspec, sizeof(pathspec));
968	if (error)
969		goto out;
970
971	cp = strchr(pathspec, '/');
972	if (cp != NULL) {
973		/* Absolute pathname */
974		mtree_current = mtree_root;
975
976		do {
977			*cp++ = '\0';
978
979			/* Disallow '.' and '..' as components. */
980			if (IS_DOT(pathspec) || IS_DOTDOT(pathspec)) {
981				mtree_error("absolute path cannot contain . "
982				    "or .. components");
983				goto out;
984			}
985
986			/* Ignore multiple adjacent slashes. */
987			if (pathspec[0] != '\0')
988				error = read_mtree_spec1(fp, false, pathspec);
989			memmove(pathspec, cp, strlen(cp) + 1);
990			cp = strchr(pathspec, '/');
991		} while (!error && cp != NULL);
992
993		/* Disallow '.' and '..' as the last component. */
994		if (!error && (IS_DOT(pathspec) || IS_DOTDOT(pathspec))) {
995			mtree_error("absolute path cannot contain . or .. "
996			    "components");
997			goto out;
998		}
999	}
1000
1001	/* Ignore absolute specfications that end with a slash. */
1002	if (!error && pathspec[0] != '\0')
1003		error = read_mtree_spec1(fp, true, pathspec);
1004
1005 out:
1006	skip_to(fp, "\n");
1007	(void)getc(fp);
1008	return (error);
1009}
1010
1011fsnode *
1012read_mtree(const char *fname, fsnode *node)
1013{
1014	struct mtree_fileinfo *fi;
1015	FILE *fp;
1016	int c, error;
1017
1018	/* We do not yet support nesting... */
1019	assert(node == NULL);
1020
1021	if (strcmp(fname, "-") == 0)
1022		fp = stdin;
1023	else {
1024		fp = fopen(fname, "r");
1025		if (fp == NULL)
1026			err(1, "Can't open `%s'", fname);
1027	}
1028
1029	error = mtree_file_push(fname, fp);
1030	if (error)
1031		goto out;
1032
1033	bzero(&mtree_global, sizeof(mtree_global));
1034	bzero(&mtree_global_inode, sizeof(mtree_global_inode));
1035	mtree_global.inode = &mtree_global_inode;
1036	mtree_global_inode.nlink = 1;
1037	mtree_global_inode.st.st_atime = mtree_global_inode.st.st_ctime =
1038	    mtree_global_inode.st.st_mtime = time(NULL);
1039	errors = warnings = 0;
1040
1041	setgroupent(1);
1042	setpassent(1);
1043
1044	mtree_root = node;
1045	mtree_current = node;
1046	do {
1047		/* Start of a new line... */
1048		fi = SLIST_FIRST(&mtree_fileinfo);
1049		fi->line++;
1050
1051		error = skip_over(fp, " \t");
1052		if (error)
1053			break;
1054
1055		c = getc(fp);
1056		if (c == EOF) {
1057			error = ferror(fp) ? errno : -1;
1058			break;
1059		}
1060
1061		switch (c) {
1062		case '\n':		/* empty line */
1063			error = 0;
1064			break;
1065		case '#':		/* comment -- skip to end of line. */
1066			error = skip_to(fp, "\n");
1067			if (!error)
1068				(void)getc(fp);
1069			break;
1070		case '/':		/* special commands */
1071			error = read_mtree_command(fp);
1072			break;
1073		default:		/* specification */
1074			ungetc(c, fp);
1075			error = read_mtree_spec(fp);
1076			break;
1077		}
1078	} while (!error);
1079
1080	endpwent();
1081	endgrent();
1082
1083	if (error <= 0 && (errors || warnings)) {
1084		warnx("%u error(s) and %u warning(s) in mtree manifest",
1085		    errors, warnings);
1086		if (errors)
1087			exit(1);
1088	}
1089
1090 out:
1091	if (error > 0)
1092		errc(1, error, "Error reading mtree file");
1093
1094	if (fp != stdin)
1095		fclose(fp);
1096
1097	if (mtree_root != NULL)
1098		return (mtree_root);
1099
1100	/* Handle empty specifications. */
1101	node = create_node(".", S_IFDIR, NULL, &mtree_global);
1102	node->first = node;
1103	return (node);
1104}
1105