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