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