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