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