common.c revision 1.1 1 /* $NetBSD: common.c,v 1.1 2018/01/09 03:31:15 christos Exp $ */
2
3 /*-
4 * Copyright (c) 2017 The NetBSD Foundation, Inc.
5 * Copyright (c) 2016 The DragonFly Project
6 * Copyright (c) 2014 The FreeBSD Foundation
7 * All rights reserved.
8 *
9 * This code is derived from software contributed to The NetBSD Foundation
10 * by Tomohiro Kusumi <kusumi.tomohiro (at) gmail.com>.
11 *
12 * This software was developed by Edward Tomasz Napierala under sponsorship
13 * from the FreeBSD Foundation.
14 *
15 * Redistribution and use in source and binary forms, with or without
16 * modification, are permitted provided that the following conditions
17 * are met:
18 * 1. Redistributions of source code must retain the above copyright
19 * notice, this list of conditions and the following disclaimer.
20 * 2. Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in the
22 * documentation and/or other materials provided with the distribution.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 *
36 * $FreeBSD: head/usr.sbin/autofs/common.c 303527 2016-07-30 01:10:05Z bapt $
37 */
38 #include <sys/cdefs.h>
39 __RCSID("$NetBSD: common.c,v 1.1 2018/01/09 03:31:15 christos Exp $");
40
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <assert.h>
44 #include <err.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <libgen.h>
48 #include <paths.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <unistd.h>
53 #include <fs/autofs/autofs_ioctl.h>
54
55 #include "common.h"
56
57 extern FILE *yyin;
58 extern char *yytext;
59 extern int yylex(void);
60
61 static void parse_master_yyin(struct node *root, const char *master);
62 static void parse_map_yyin(struct node *parent, const char *map,
63 const char *executable_key);
64
65 char *
66 checked_strdup(const char *s)
67 {
68 char *c;
69
70 assert(s != NULL);
71
72 c = strdup(s);
73 if (c == NULL)
74 log_err(1, "strdup");
75 return c;
76 }
77
78 /*
79 * Concatenate two strings, inserting separator between them, unless not needed.
80 */
81 char *
82 concat(const char *s1, char separator, const char *s2)
83 {
84 char *result;
85 char s1last, s2first;
86 int ret;
87
88 if (s1 == NULL)
89 s1 = "";
90 if (s2 == NULL)
91 s2 = "";
92
93 if (s1[0] == '\0')
94 s1last = '\0';
95 else
96 s1last = s1[strlen(s1) - 1];
97
98 s2first = s2[0];
99
100 if (s1last == separator && s2first == separator) {
101 /*
102 * If s1 ends with the separator and s2 begins with
103 * it - skip the latter; otherwise concatenating "/"
104 * and "/foo" would end up returning "//foo".
105 */
106 ret = asprintf(&result, "%s%s", s1, s2 + 1);
107 } else if (s1last == separator || s2first == separator ||
108 s1[0] == '\0' || s2[0] == '\0') {
109 ret = asprintf(&result, "%s%s", s1, s2);
110 } else {
111 ret = asprintf(&result, "%s%c%s", s1, separator, s2);
112 }
113 if (ret < 0)
114 log_err(1, "asprintf");
115
116 #if 0
117 log_debugx("%s: got %s and %s, returning %s", __func__, s1, s2, result);
118 #endif
119
120 return result;
121 }
122
123 void
124 create_directory(const char *path)
125 {
126 char *component, *copy, *tofree, *partial, *tmp;
127 int error;
128
129 assert(path[0] == '/');
130
131 /*
132 * +1 to skip the leading slash.
133 */
134 copy = tofree = checked_strdup(path + 1);
135
136 partial = checked_strdup("");
137 for (;;) {
138 component = strsep(©, "/");
139 if (component == NULL)
140 break;
141 tmp = concat(partial, '/', component);
142 free(partial);
143 partial = tmp;
144 //log_debugx("creating \"%s\"", partial);
145 error = mkdir(partial, 0755);
146 if (error != 0 && errno != EEXIST) {
147 log_warn("cannot create %s", partial);
148 return;
149 }
150 }
151
152 free(tofree);
153 }
154
155 struct node *
156 node_new_root(void)
157 {
158 struct node *n;
159
160 n = calloc(1, sizeof(*n));
161 if (n == NULL)
162 log_err(1, "calloc");
163 // XXX
164 n->n_key = checked_strdup("/");
165 n->n_options = checked_strdup("");
166
167 TAILQ_INIT(&n->n_children);
168
169 return n;
170 }
171
172 struct node *
173 node_new(struct node *parent, char *key, char *options, char *location,
174 const char *config_file, int config_line)
175 {
176 struct node *n;
177
178 n = calloc(1, sizeof(*n));
179 if (n == NULL)
180 log_err(1, "calloc");
181
182 TAILQ_INIT(&n->n_children);
183 assert(key != NULL);
184 assert(key[0] != '\0');
185 n->n_key = key;
186 if (options != NULL)
187 n->n_options = options;
188 else
189 n->n_options = strdup("");
190 n->n_location = location;
191 assert(config_file != NULL);
192 n->n_config_file = config_file;
193 assert(config_line >= 0);
194 n->n_config_line = config_line;
195
196 assert(parent != NULL);
197 n->n_parent = parent;
198 TAILQ_INSERT_TAIL(&parent->n_children, n, n_next);
199
200 return n;
201 }
202
203 struct node *
204 node_new_map(struct node *parent, char *key, char *options, char *map,
205 const char *config_file, int config_line)
206 {
207 struct node *n;
208
209 n = calloc(1, sizeof(*n));
210 if (n == NULL)
211 log_err(1, "calloc");
212
213 TAILQ_INIT(&n->n_children);
214 assert(key != NULL);
215 assert(key[0] != '\0');
216 n->n_key = key;
217 if (options != NULL)
218 n->n_options = options;
219 else
220 n->n_options = strdup("");
221 n->n_map = map;
222 assert(config_file != NULL);
223 n->n_config_file = config_file;
224 assert(config_line >= 0);
225 n->n_config_line = config_line;
226
227 assert(parent != NULL);
228 n->n_parent = parent;
229 TAILQ_INSERT_TAIL(&parent->n_children, n, n_next);
230
231 return n;
232 }
233
234 static struct node *
235 node_duplicate(const struct node *o, struct node *parent)
236 {
237 const struct node *child;
238 struct node *n;
239
240 if (parent == NULL)
241 parent = o->n_parent;
242
243 n = node_new(parent, o->n_key, o->n_options, o->n_location,
244 o->n_config_file, o->n_config_line);
245
246 TAILQ_FOREACH(child, &o->n_children, n_next)
247 node_duplicate(child, n);
248
249 return n;
250 }
251
252 static void
253 node_delete(struct node *n)
254 {
255 struct node *child, *tmp;
256
257 assert (n != NULL);
258
259 TAILQ_FOREACH_SAFE(child, &n->n_children, n_next, tmp)
260 node_delete(child);
261
262 if (n->n_parent != NULL)
263 TAILQ_REMOVE(&n->n_parent->n_children, n, n_next);
264
265 free(n);
266 }
267
268 /*
269 * Move (reparent) node 'n' to make it sibling of 'previous', placed
270 * just after it.
271 */
272 static void
273 node_move_after(struct node *n, struct node *previous)
274 {
275
276 TAILQ_REMOVE(&n->n_parent->n_children, n, n_next);
277 n->n_parent = previous->n_parent;
278 TAILQ_INSERT_AFTER(&previous->n_parent->n_children, previous, n, n_next);
279 }
280
281 static void
282 node_expand_includes(struct node *root, bool is_master)
283 {
284 struct node *n, *n2, *tmp, *tmp2, *tmproot;
285 int error;
286
287 TAILQ_FOREACH_SAFE(n, &root->n_children, n_next, tmp) {
288 if (n->n_key[0] != '+')
289 continue;
290
291 error = access(AUTO_INCLUDE_PATH, F_OK);
292 if (error != 0) {
293 log_errx(1, "directory services not configured; "
294 "%s does not exist", AUTO_INCLUDE_PATH);
295 }
296
297 /*
298 * "+1" to skip leading "+".
299 */
300 yyin = auto_popen(AUTO_INCLUDE_PATH, n->n_key + 1, NULL);
301 assert(yyin != NULL);
302
303 tmproot = node_new_root();
304 if (is_master)
305 parse_master_yyin(tmproot, n->n_key);
306 else
307 parse_map_yyin(tmproot, n->n_key, NULL);
308
309 error = auto_pclose(yyin);
310 yyin = NULL;
311 if (error != 0) {
312 log_errx(1, "failed to handle include \"%s\"",
313 n->n_key);
314 }
315
316 /*
317 * Entries to be included are now in tmproot. We need to merge
318 * them with the rest, preserving their place and ordering.
319 */
320 TAILQ_FOREACH_REVERSE_SAFE(n2,
321 &tmproot->n_children, nodehead, n_next, tmp2) {
322 node_move_after(n2, n);
323 }
324
325 node_delete(n);
326 node_delete(tmproot);
327 }
328 }
329
330 static char *
331 expand_ampersand(char *string, const char *key)
332 {
333 char c, *expanded;
334 int ret;
335 size_t i, before_len = 0;
336 bool backslashed = false;
337
338 assert(key[0] != '\0');
339
340 expanded = checked_strdup(string);
341
342 for (i = 0; string[i] != '\0'; i++) {
343 c = string[i];
344 if (c == '\\' && backslashed == false) {
345 backslashed = true;
346 continue;
347 }
348 if (backslashed) {
349 backslashed = false;
350 continue;
351 }
352 backslashed = false;
353 if (c != '&')
354 continue;
355
356 /*
357 * The 'before_len' variable contains the number
358 * of characters before the '&'.
359 */
360 before_len = i;
361 //assert(i + 1 < strlen(string));
362
363 ret = asprintf(&expanded, "%.*s%s%s",
364 (int)before_len, string, key, string + before_len + 1);
365 if (ret < 0)
366 log_err(1, "asprintf");
367
368 //log_debugx("\"%s\" expanded with key \"%s\" to \"%s\"",
369 // string, key, expanded);
370
371 /*
372 * Figure out where to start searching for next variable.
373 */
374 string = expanded;
375 i = before_len + strlen(key);
376 backslashed = false;
377 //assert(i < strlen(string));
378 }
379
380 return expanded;
381 }
382
383 /*
384 * Expand "&" in n_location. If the key is NULL, try to use
385 * key from map entries themselves. Keep in mind that maps
386 * consist of tho levels of node structures, the key is one
387 * level up.
388 *
389 * Variant with NULL key is for "automount -LL".
390 */
391 void
392 node_expand_ampersand(struct node *n, const char *key)
393 {
394 struct node *child;
395
396 if (n->n_location != NULL) {
397 if (key == NULL) {
398 if (n->n_parent != NULL &&
399 strcmp(n->n_parent->n_key, "*") != 0) {
400 n->n_location = expand_ampersand(n->n_location,
401 n->n_parent->n_key);
402 }
403 } else {
404 n->n_location = expand_ampersand(n->n_location, key);
405 }
406 }
407
408 TAILQ_FOREACH(child, &n->n_children, n_next)
409 node_expand_ampersand(child, key);
410 }
411
412 /*
413 * Expand "*" in n_key.
414 */
415 void
416 node_expand_wildcard(struct node *n, const char *key)
417 {
418 struct node *child, *expanded;
419
420 assert(key != NULL);
421
422 if (strcmp(n->n_key, "*") == 0) {
423 expanded = node_duplicate(n, NULL);
424 expanded->n_key = checked_strdup(key);
425 node_move_after(expanded, n);
426 }
427
428 TAILQ_FOREACH(child, &n->n_children, n_next)
429 node_expand_wildcard(child, key);
430 }
431
432 int
433 node_expand_defined(struct node *n)
434 {
435 struct node *child;
436 int error, cummulative_error = 0;
437
438 if (n->n_location != NULL) {
439 n->n_location = defined_expand(n->n_location);
440 if (n->n_location == NULL) {
441 log_warnx("failed to expand location for %s",
442 node_path(n));
443 return EINVAL;
444 }
445 }
446
447 TAILQ_FOREACH(child, &n->n_children, n_next) {
448 error = node_expand_defined(child);
449 if (error != 0 && cummulative_error == 0)
450 cummulative_error = error;
451 }
452
453 return cummulative_error;
454 }
455
456 static bool
457 node_is_direct_key(const struct node *n)
458 {
459
460 return n->n_parent != NULL && n->n_parent->n_parent == NULL &&
461 strcmp(n->n_key, "/-") == 0;
462 }
463
464 bool
465 node_is_direct_map(const struct node *n)
466 {
467
468 for (;;) {
469 assert(n->n_parent != NULL);
470 if (n->n_parent->n_parent == NULL)
471 break;
472 n = n->n_parent;
473 }
474
475 return node_is_direct_key(n);
476 }
477
478 bool
479 node_has_wildcards(const struct node *n)
480 {
481 const struct node *child;
482
483 TAILQ_FOREACH(child, &n->n_children, n_next) {
484 if (strcmp(child->n_key, "*") == 0)
485 return true;
486 }
487
488 return false;
489 }
490
491 static void
492 node_expand_maps(struct node *n, bool indirect)
493 {
494 struct node *child, *tmp;
495
496 TAILQ_FOREACH_SAFE(child, &n->n_children, n_next, tmp) {
497 if (node_is_direct_map(child)) {
498 if (indirect)
499 continue;
500 } else {
501 if (indirect == false)
502 continue;
503 }
504
505 /*
506 * This is the first-level map node; the one that contains
507 * the key and subnodes with mountpoints and actual map names.
508 */
509 if (child->n_map == NULL)
510 continue;
511
512 if (indirect) {
513 log_debugx("map \"%s\" is an indirect map, parsing",
514 child->n_map);
515 } else {
516 log_debugx("map \"%s\" is a direct map, parsing",
517 child->n_map);
518 }
519 parse_map(child, child->n_map, NULL, NULL);
520 }
521 }
522
523 static void
524 node_expand_direct_maps(struct node *n)
525 {
526
527 node_expand_maps(n, false);
528 }
529
530 void
531 node_expand_indirect_maps(struct node *n)
532 {
533
534 node_expand_maps(n, true);
535 }
536
537 static char *
538 node_path_x(const struct node *n, char *x)
539 {
540 char *path;
541
542 if (n->n_parent == NULL)
543 return x;
544
545 /*
546 * Return "/-" for direct maps only if we were asked for path
547 * to the "/-" node itself, not to any of its subnodes.
548 */
549 if (node_is_direct_key(n) && x[0] != '\0')
550 return x;
551
552 assert(n->n_key[0] != '\0');
553 path = concat(n->n_key, '/', x);
554 free(x);
555
556 return node_path_x(n->n_parent, path);
557 }
558
559 /*
560 * Return full path for node, consisting of concatenated
561 * paths of node itself and all its parents, up to the root.
562 */
563 char *
564 node_path(const struct node *n)
565 {
566 char *path;
567 size_t len;
568
569 path = node_path_x(n, checked_strdup(""));
570
571 /*
572 * Strip trailing slash, unless the whole path is "/".
573 */
574 len = strlen(path);
575 if (len > 1 && path[len - 1] == '/')
576 path[len - 1] = '\0';
577
578 return path;
579 }
580
581 static char *
582 node_options_x(const struct node *n, char *x)
583 {
584 char *options;
585
586 if (n == NULL)
587 return x;
588
589 options = concat(x, ',', n->n_options);
590 free(x);
591
592 return node_options_x(n->n_parent, options);
593 }
594
595 /*
596 * Return options for node, consisting of concatenated
597 * options from the node itself and all its parents,
598 * up to the root.
599 */
600 char *
601 node_options(const struct node *n)
602 {
603
604 return node_options_x(n, checked_strdup(""));
605 }
606
607 static void
608 node_print_indent(const struct node *n, const char *cmdline_options,
609 int indent)
610 {
611 const struct node *child, *first_child;
612 char *path, *options, *tmp;
613
614 path = node_path(n);
615 tmp = node_options(n);
616 options = concat(cmdline_options, ',', tmp);
617 free(tmp);
618
619 /*
620 * Do not show both parent and child node if they have the same
621 * mountpoint; only show the child node. This means the typical,
622 * "key location", map entries are shown in a single line;
623 * the "key mountpoint1 location2 mountpoint2 location2" entries
624 * take multiple lines.
625 */
626 first_child = TAILQ_FIRST(&n->n_children);
627 if (first_child == NULL || TAILQ_NEXT(first_child, n_next) != NULL ||
628 strcmp(path, node_path(first_child)) != 0) {
629 assert(n->n_location == NULL || n->n_map == NULL);
630 printf("%*.s%-*s %s%-*s %-*s # %s map %s at %s:%d\n",
631 indent, "",
632 25 - indent,
633 path,
634 options[0] != '\0' ? "-" : " ",
635 20,
636 options[0] != '\0' ? options : "",
637 20,
638 n->n_location != NULL ? n->n_location : n->n_map != NULL ? n->n_map : "",
639 node_is_direct_map(n) ? "direct" : "indirect",
640 indent == 0 ? "referenced" : "defined",
641 n->n_config_file, n->n_config_line);
642 }
643
644 free(path);
645 free(options);
646
647 TAILQ_FOREACH(child, &n->n_children, n_next)
648 node_print_indent(child, cmdline_options, indent + 2);
649 }
650
651 /*
652 * Recursively print node with all its children. The cmdline_options
653 * argument is used for additional options to be prepended to all the
654 * others - usually those are the options passed by command line.
655 */
656 void
657 node_print(const struct node *n, const char *cmdline_options)
658 {
659 const struct node *child;
660
661 TAILQ_FOREACH(child, &n->n_children, n_next)
662 node_print_indent(child, cmdline_options, 0);
663 }
664
665 static struct node *
666 node_find_x(struct node *node, const char *path)
667 {
668 struct node *child, *found;
669 char *tmp;
670 size_t tmplen;
671
672 //log_debugx("looking up %s in %s", path, node_path(node));
673
674 if (!node_is_direct_key(node)) {
675 tmp = node_path(node);
676 tmplen = strlen(tmp);
677 if (strncmp(tmp, path, tmplen) != 0) {
678 free(tmp);
679 return NULL;
680 }
681 if (path[tmplen] != '/' && path[tmplen] != '\0') {
682 /*
683 * If we have two map entries like 'foo' and 'foobar', make
684 * sure the search for 'foobar' won't match 'foo' instead.
685 */
686 free(tmp);
687 return NULL;
688 }
689 free(tmp);
690 }
691
692 TAILQ_FOREACH(child, &node->n_children, n_next) {
693 found = node_find_x(child, path);
694 if (found != NULL)
695 return found;
696 }
697
698 if (node->n_parent == NULL || node_is_direct_key(node))
699 return NULL;
700
701 return node;
702 }
703
704 struct node *
705 node_find(struct node *root, const char *path)
706 {
707 struct node *node;
708
709 assert(root->n_parent == NULL);
710
711 node = node_find_x(root, path);
712 if (node != NULL)
713 assert(node != root);
714
715 return node;
716 }
717
718 /*
719 * Canonical form of a map entry looks like this:
720 *
721 * key [-options] [ [/mountpoint] [-options2] location ... ]
722 *
723 * Entries for executable maps are slightly different, as they
724 * lack the 'key' field and are always single-line; the key field
725 * for those maps is taken from 'executable_key' argument.
726 *
727 * We parse it in such a way that a map always has two levels - first
728 * for key, and the second, for the mountpoint.
729 */
730 static void
731 parse_map_yyin(struct node *parent, const char *map, const char *executable_key)
732 {
733 char *key = NULL, *options = NULL, *mountpoint = NULL,
734 *options2 = NULL, *location = NULL;
735 int ret;
736 struct node *node;
737
738 lineno = 1;
739
740 if (executable_key != NULL)
741 key = checked_strdup(executable_key);
742
743 for (;;) {
744 ret = yylex();
745 if (ret == 0 || ret == NEWLINE) {
746 /*
747 * In case of executable map, the key is always
748 * non-NULL, even if the map is empty. So, make sure
749 * we don't fail empty maps here.
750 */
751 if ((key != NULL && executable_key == NULL) ||
752 options != NULL) {
753 log_errx(1, "truncated entry at %s, line %d",
754 map, lineno);
755 }
756 if (ret == 0 || executable_key != NULL) {
757 /*
758 * End of file.
759 */
760 break;
761 } else {
762 key = options = NULL;
763 continue;
764 }
765 }
766 if (key == NULL) {
767 key = checked_strdup(yytext);
768 if (key[0] == '+') {
769 node_new(parent, key, NULL, NULL, map, lineno);
770 key = options = NULL;
771 continue;
772 }
773 continue;
774 } else if (yytext[0] == '-') {
775 if (options != NULL) {
776 log_errx(1, "duplicated options at %s, line %d",
777 map, lineno);
778 }
779 /*
780 * +1 to skip leading "-".
781 */
782 options = checked_strdup(yytext + 1);
783 continue;
784 }
785
786 /*
787 * We cannot properly handle a situation where the map key
788 * is "/". Ignore such entries.
789 *
790 * XXX: According to Piete Brooks, Linux automounter uses
791 * "/" as a wildcard character in LDAP maps. Perhaps
792 * we should work around this braindamage by substituting
793 * "*" for "/"?
794 */
795 if (strcmp(key, "/") == 0) {
796 log_warnx("nonsensical map key \"/\" at %s, line %d; "
797 "ignoring map entry ", map, lineno);
798
799 /*
800 * Skip the rest of the entry.
801 */
802 do {
803 ret = yylex();
804 } while (ret != 0 && ret != NEWLINE);
805
806 key = options = NULL;
807 continue;
808 }
809
810 //log_debugx("adding map node, %s", key);
811 node = node_new(parent, key, options, NULL, map, lineno);
812 key = options = NULL;
813
814 for (;;) {
815 if (yytext[0] == '/') {
816 if (mountpoint != NULL) {
817 log_errx(1, "duplicated mountpoint "
818 "in %s, line %d", map, lineno);
819 }
820 if (options2 != NULL || location != NULL) {
821 log_errx(1, "mountpoint out of order "
822 "in %s, line %d", map, lineno);
823 }
824 mountpoint = checked_strdup(yytext);
825 goto again;
826 }
827
828 if (yytext[0] == '-') {
829 if (options2 != NULL) {
830 log_errx(1, "duplicated options "
831 "in %s, line %d", map, lineno);
832 }
833 if (location != NULL) {
834 log_errx(1, "options out of order "
835 "in %s, line %d", map, lineno);
836 }
837 options2 = checked_strdup(yytext + 1);
838 goto again;
839 }
840
841 if (location != NULL) {
842 log_errx(1, "too many arguments "
843 "in %s, line %d", map, lineno);
844 }
845
846 /*
847 * If location field starts with colon, e.g. ":/dev/cd0",
848 * then strip it.
849 */
850 if (yytext[0] == ':') {
851 location = checked_strdup(yytext + 1);
852 if (location[0] == '\0') {
853 log_errx(1, "empty location in %s, "
854 "line %d", map, lineno);
855 }
856 } else {
857 location = checked_strdup(yytext);
858 }
859
860 if (mountpoint == NULL)
861 mountpoint = checked_strdup("/");
862 if (options2 == NULL)
863 options2 = checked_strdup("");
864
865 #if 0
866 log_debugx("adding map node, %s %s %s",
867 mountpoint, options2, location);
868 #endif
869 node_new(node, mountpoint, options2, location,
870 map, lineno);
871 mountpoint = options2 = location = NULL;
872 again:
873 ret = yylex();
874 if (ret == 0 || ret == NEWLINE) {
875 if (mountpoint != NULL || options2 != NULL ||
876 location != NULL) {
877 log_errx(1, "truncated entry "
878 "in %s, line %d", map, lineno);
879 }
880 break;
881 }
882 }
883 }
884 }
885
886 /*
887 * Parse output of a special map called without argument. It is a list
888 * of keys, separated by newlines. They can contain whitespace, so use
889 * getline(3) instead of lexer used for maps.
890 */
891 static void
892 parse_map_keys_yyin(struct node *parent, const char *map)
893 {
894 char *line = NULL, *key;
895 size_t linecap = 0;
896 ssize_t linelen;
897
898 lineno = 1;
899
900 for (;;) {
901 linelen = getline(&line, &linecap, yyin);
902 if (linelen < 0) {
903 /*
904 * End of file.
905 */
906 break;
907 }
908 if (linelen <= 1) {
909 /*
910 * Empty line, consisting of just the newline.
911 */
912 continue;
913 }
914
915 /*
916 * "-1" to strip the trailing newline.
917 */
918 key = strndup(line, (size_t)linelen - 1);
919
920 log_debugx("adding key \"%s\"", key);
921 node_new(parent, key, NULL, NULL, map, lineno);
922 lineno++;
923 }
924 free(line);
925 }
926
927 static bool
928 file_is_executable(const char *path)
929 {
930 struct stat sb;
931 int error;
932
933 error = stat(path, &sb);
934 if (error != 0)
935 log_err(1, "cannot stat %s", path);
936 return (sb.st_mode & S_IXUSR) || (sb.st_mode & S_IXGRP) ||
937 (sb.st_mode & S_IXOTH);
938 }
939
940 /*
941 * Parse a special map, e.g. "-hosts".
942 */
943 static void
944 parse_special_map(struct node *parent, const char *map, const char *key)
945 {
946 char *path;
947 int error, ret;
948
949 assert(map[0] == '-');
950
951 /*
952 * +1 to skip leading "-" in map name.
953 */
954 ret = asprintf(&path, "%s/special_%s", AUTO_SPECIAL_PREFIX, map + 1);
955 if (ret < 0)
956 log_err(1, "asprintf");
957
958 yyin = auto_popen(path, key, NULL);
959 assert(yyin != NULL);
960
961 if (key == NULL) {
962 parse_map_keys_yyin(parent, map);
963 } else {
964 parse_map_yyin(parent, map, key);
965 }
966
967 error = auto_pclose(yyin);
968 yyin = NULL;
969 if (error != 0)
970 log_errx(1, "failed to handle special map \"%s\"", map);
971
972 node_expand_includes(parent, false);
973 node_expand_direct_maps(parent);
974
975 free(path);
976 }
977
978 /*
979 * Retrieve and parse map from directory services, e.g. LDAP.
980 * Note that it is different from executable maps, in that
981 * the include script outputs the whole map to standard output
982 * (as opposed to executable maps that only output a single
983 * entry, without the key), and it takes the map name as an
984 * argument, instead of key.
985 */
986 static void
987 parse_included_map(struct node *parent, const char *map)
988 {
989 int error;
990
991 assert(map[0] != '-');
992 assert(map[0] != '/');
993
994 error = access(AUTO_INCLUDE_PATH, F_OK);
995 if (error != 0) {
996 log_errx(1, "directory services not configured;"
997 " %s does not exist", AUTO_INCLUDE_PATH);
998 }
999
1000 yyin = auto_popen(AUTO_INCLUDE_PATH, map, NULL);
1001 assert(yyin != NULL);
1002
1003 parse_map_yyin(parent, map, NULL);
1004
1005 error = auto_pclose(yyin);
1006 yyin = NULL;
1007 if (error != 0)
1008 log_errx(1, "failed to handle remote map \"%s\"", map);
1009
1010 node_expand_includes(parent, false);
1011 node_expand_direct_maps(parent);
1012 }
1013
1014 void
1015 parse_map(struct node *parent, const char *map, const char *key,
1016 bool *wildcards)
1017 {
1018 char *path = NULL;
1019 int error, ret;
1020 bool executable;
1021
1022 assert(map != NULL);
1023 assert(map[0] != '\0');
1024
1025 log_debugx("parsing map \"%s\"", map);
1026
1027 if (wildcards != NULL)
1028 *wildcards = false;
1029
1030 if (map[0] == '-') {
1031 if (wildcards != NULL)
1032 *wildcards = true;
1033 return parse_special_map(parent, map, key);
1034 }
1035
1036 if (map[0] == '/') {
1037 path = checked_strdup(map);
1038 } else {
1039 ret = asprintf(&path, "%s/%s", AUTO_MAP_PREFIX, map);
1040 if (ret < 0)
1041 log_err(1, "asprintf");
1042 log_debugx("map \"%s\" maps to \"%s\"", map, path);
1043
1044 /*
1045 * See if the file exists. If not, try to obtain the map
1046 * from directory services.
1047 */
1048 error = access(path, F_OK);
1049 if (error != 0) {
1050 log_debugx("map file \"%s\" does not exist; falling "
1051 "back to directory services", path);
1052 return parse_included_map(parent, map);
1053 }
1054 }
1055
1056 executable = file_is_executable(path);
1057
1058 if (executable) {
1059 log_debugx("map \"%s\" is executable", map);
1060
1061 if (wildcards != NULL)
1062 *wildcards = true;
1063
1064 if (key != NULL) {
1065 yyin = auto_popen(path, key, NULL);
1066 } else {
1067 yyin = auto_popen(path, NULL);
1068 }
1069 assert(yyin != NULL);
1070 } else {
1071 yyin = fopen(path, "r");
1072 if (yyin == NULL)
1073 log_err(1, "unable to open \"%s\"", path);
1074 }
1075
1076 free(path);
1077 path = NULL;
1078
1079 parse_map_yyin(parent, map, executable ? key : NULL);
1080
1081 if (executable) {
1082 error = auto_pclose(yyin);
1083 yyin = NULL;
1084 if (error != 0) {
1085 log_errx(1, "failed to handle executable map \"%s\"",
1086 map);
1087 }
1088 } else {
1089 fclose(yyin);
1090 }
1091 yyin = NULL;
1092
1093 log_debugx("done parsing map \"%s\"", map);
1094
1095 node_expand_includes(parent, false);
1096 node_expand_direct_maps(parent);
1097 }
1098
1099 static void
1100 parse_master_yyin(struct node *root, const char *master)
1101 {
1102 char *mountpoint = NULL, *map = NULL, *options = NULL;
1103 int ret;
1104
1105 /*
1106 * XXX: 1 gives incorrect values; wtf?
1107 */
1108 lineno = 0;
1109
1110 for (;;) {
1111 ret = yylex();
1112 if (ret == 0 || ret == NEWLINE) {
1113 if (mountpoint != NULL) {
1114 //log_debugx("adding map for %s", mountpoint);
1115 node_new_map(root, mountpoint, options, map,
1116 master, lineno);
1117 }
1118 if (ret == 0) {
1119 break;
1120 } else {
1121 mountpoint = map = options = NULL;
1122 continue;
1123 }
1124 }
1125 if (mountpoint == NULL) {
1126 mountpoint = checked_strdup(yytext);
1127 } else if (map == NULL) {
1128 map = checked_strdup(yytext);
1129 } else if (options == NULL) {
1130 /*
1131 * +1 to skip leading "-".
1132 */
1133 options = checked_strdup(yytext + 1);
1134 } else {
1135 log_errx(1, "too many arguments at %s, line %d",
1136 master, lineno);
1137 }
1138 }
1139 }
1140
1141 void
1142 parse_master(struct node *root, const char *master)
1143 {
1144
1145 log_debugx("parsing auto_master file at \"%s\"", master);
1146
1147 yyin = fopen(master, "r");
1148 if (yyin == NULL)
1149 err(1, "unable to open %s", master);
1150
1151 parse_master_yyin(root, master);
1152
1153 fclose(yyin);
1154 yyin = NULL;
1155
1156 log_debugx("done parsing \"%s\"", master);
1157
1158 node_expand_includes(root, true);
1159 node_expand_direct_maps(root);
1160 }
1161
1162 /*
1163 * Two things daemon(3) does, that we actually also want to do
1164 * when running in foreground, is closing the stdin and chdiring
1165 * to "/". This is what we do here.
1166 */
1167 void
1168 lesser_daemon(void)
1169 {
1170 int error, fd;
1171
1172 error = chdir("/");
1173 if (error != 0)
1174 log_warn("chdir");
1175
1176 fd = open(_PATH_DEVNULL, O_RDWR, 0);
1177 if (fd < 0) {
1178 log_warn("cannot open %s", _PATH_DEVNULL);
1179 return;
1180 }
1181
1182 error = dup2(fd, STDIN_FILENO);
1183 if (error != 0)
1184 log_warn("dup2");
1185
1186 error = close(fd);
1187 if (error != 0) {
1188 /* Bloody hell. */
1189 log_warn("close");
1190 }
1191 }
1192
1193 int
1194 main(int argc, char **argv)
1195 {
1196 char *cmdname;
1197
1198 if (argv[0] == NULL)
1199 log_errx(1, "NULL command name");
1200
1201 cmdname = basename(argv[0]);
1202
1203 if (strcmp(cmdname, "automount") == 0)
1204 return main_automount(argc, argv);
1205 else if (strcmp(cmdname, "automountd") == 0)
1206 return main_automountd(argc, argv);
1207 else if (strcmp(cmdname, "autounmountd") == 0)
1208 return main_autounmountd(argc, argv);
1209 else
1210 log_errx(1, "binary name should be either \"automount\", "
1211 "\"automountd\", or \"autounmountd\"");
1212 }
1213