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