root/usr.sbin/autofs/common.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2014 The FreeBSD Foundation
 *
 * This software was developed by Edward Tomasz Napierala under sponsorship
 * from the FreeBSD Foundation.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/linker.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/utsname.h>
#include <assert.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <libutil.h>
#include <netdb.h>
#include <paths.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "autofs_ioctl.h"

#include "common.h"

extern FILE *yyin;
extern char *yytext;
extern int yylex(void);

static void     parse_master_yyin(struct node *root, const char *master);
static void     parse_map_yyin(struct node *parent, const char *map,
                    const char *executable_key);

char *
checked_strdup(const char *s)
{
        char *c;

        assert(s != NULL);

        c = strdup(s);
        if (c == NULL)
                log_err(1, "strdup");
        return (c);
}

/*
 * Concatenate two strings, inserting separator between them, unless not needed.
 */
char *
concat(const char *s1, char separator, const char *s2)
{
        char *result;
        char s1last, s2first;
        int ret;

        if (s1 == NULL)
                s1 = "";
        if (s2 == NULL)
                s2 = "";

        if (s1[0] == '\0')
                s1last = '\0';
        else
                s1last = s1[strlen(s1) - 1];

        s2first = s2[0];

        if (s1last == separator && s2first == separator) {
                /*
                 * If s1 ends with the separator and s2 begins with
                 * it - skip the latter; otherwise concatenating "/"
                 * and "/foo" would end up returning "//foo".
                 */
                ret = asprintf(&result, "%s%s", s1, s2 + 1);
        } else if (s1last == separator || s2first == separator ||
            s1[0] == '\0' || s2[0] == '\0') {
                ret = asprintf(&result, "%s%s", s1, s2);
        } else {
                ret = asprintf(&result, "%s%c%s", s1, separator, s2);
        }
        if (ret < 0)
                log_err(1, "asprintf");

        //log_debugx("%s: got %s and %s, returning %s", __func__, s1, s2, result);

        return (result);
}

void
create_directory(const char *path)
{
        char *component, *copy, *tofree, *partial, *tmp;
        int error;

        assert(path[0] == '/');

        /*
         * +1 to skip the leading slash.
         */
        copy = tofree = checked_strdup(path + 1);

        partial = checked_strdup("/");
        for (;;) {
                component = strsep(&copy, "/");
                if (component == NULL)
                        break;
                tmp = concat(partial, '/', component);
                free(partial);
                partial = tmp;
                //log_debugx("creating \"%s\"", partial);
                error = mkdir(partial, 0755);
                if (error != 0 && errno != EEXIST) {
                        log_warn("cannot create %s", partial);
                        break;
                }
        }

        free(partial);
        free(tofree);
}

struct node *
node_new_root(void)
{
        struct node *n;

        n = calloc(1, sizeof(*n));
        if (n == NULL)
                log_err(1, "calloc");
        // XXX
        n->n_key = checked_strdup("/");
        n->n_options = checked_strdup("");

        TAILQ_INIT(&n->n_children);

        return (n);
}

struct node *
node_new(struct node *parent, char *key, char *options, char *location,
    const char *config_file, int config_line)
{
        struct node *n;

        n = calloc(1, sizeof(*n));
        if (n == NULL)
                log_err(1, "calloc");

        TAILQ_INIT(&n->n_children);
        assert(key != NULL);
        assert(key[0] != '\0');
        n->n_key = key;
        if (options != NULL)
                n->n_options = options;
        else
                n->n_options = strdup("");
        n->n_location = location;
        assert(config_file != NULL);
        n->n_config_file = config_file;
        assert(config_line >= 0);
        n->n_config_line = config_line;

        assert(parent != NULL);
        n->n_parent = parent;
        TAILQ_INSERT_TAIL(&parent->n_children, n, n_next);

        return (n);
}

struct node *
node_new_map(struct node *parent, char *key, char *options, char *map,
    const char *config_file, int config_line)
{
        struct node *n;

        n = calloc(1, sizeof(*n));
        if (n == NULL)
                log_err(1, "calloc");

        TAILQ_INIT(&n->n_children);
        assert(key != NULL);
        assert(key[0] != '\0');
        n->n_key = key;
        if (options != NULL)
                n->n_options = options;
        else
                n->n_options = strdup("");
        n->n_map = map;
        assert(config_file != NULL);
        n->n_config_file = config_file;
        assert(config_line >= 0);
        n->n_config_line = config_line;

        assert(parent != NULL);
        n->n_parent = parent;
        TAILQ_INSERT_TAIL(&parent->n_children, n, n_next);

        return (n);
}

static struct node *
node_duplicate(const struct node *o, struct node *parent)
{
        const struct node *child;
        struct node *n;

        if (parent == NULL)
                parent = o->n_parent;

        n = node_new(parent, o->n_key, o->n_options, o->n_location,
            o->n_config_file, o->n_config_line);

        TAILQ_FOREACH(child, &o->n_children, n_next)
                node_duplicate(child, n);

        return (n);
}

static void
node_delete(struct node *n)
{
        struct node *child, *tmp;

        assert (n != NULL);

        TAILQ_FOREACH_SAFE(child, &n->n_children, n_next, tmp)
                node_delete(child);

        if (n->n_parent != NULL)
                TAILQ_REMOVE(&n->n_parent->n_children, n, n_next);

        free(n);
}

/*
 * Move (reparent) node 'n' to make it sibling of 'previous', placed
 * just after it.
 */
static void
node_move_after(struct node *n, struct node *previous)
{

        TAILQ_REMOVE(&n->n_parent->n_children, n, n_next);
        n->n_parent = previous->n_parent;
        TAILQ_INSERT_AFTER(&previous->n_parent->n_children, previous, n, n_next);
}

static void
node_expand_includes(struct node *root, bool is_master)
{
        struct node *n, *n2, *tmp, *tmp2, *tmproot;
        int error;

        TAILQ_FOREACH_SAFE(n, &root->n_children, n_next, tmp) {
                if (n->n_key[0] != '+')
                        continue;

                error = access(AUTO_INCLUDE_PATH, F_OK);
                if (error != 0) {
                        log_errx(1, "directory services not configured; "
                            "%s does not exist", AUTO_INCLUDE_PATH);
                }

                /*
                 * "+1" to skip leading "+".
                 */
                yyin = auto_popen(AUTO_INCLUDE_PATH, n->n_key + 1, NULL);
                assert(yyin != NULL);

                tmproot = node_new_root();
                if (is_master)
                        parse_master_yyin(tmproot, n->n_key);
                else
                        parse_map_yyin(tmproot, n->n_key, NULL);

                error = auto_pclose(yyin);
                yyin = NULL;
                if (error != 0) {
                        log_errx(1, "failed to handle include \"%s\"",
                            n->n_key);
                }

                /*
                 * Entries to be included are now in tmproot.  We need to merge
                 * them with the rest, preserving their place and ordering.
                 */
                TAILQ_FOREACH_REVERSE_SAFE(n2,
                    &tmproot->n_children, nodehead, n_next, tmp2) {
                        node_move_after(n2, n);
                }

                node_delete(n);
                node_delete(tmproot);
        }
}

static char *
expand_ampersand(char *string, const char *key)
{
        char c, *expanded;
        int i, ret, before_len = 0;
        bool backslashed = false;

        assert(key[0] != '\0');

        expanded = checked_strdup(string);

        for (i = 0; string[i] != '\0'; i++) {
                c = string[i];
                if (c == '\\' && backslashed == false) {
                        backslashed = true;
                        continue;
                }
                if (backslashed) {
                        backslashed = false;
                        continue;
                }
                backslashed = false;
                if (c != '&')
                        continue;

                /*
                 * The 'before_len' variable contains the number
                 * of characters before the '&'.
                 */
                before_len = i;
                //assert(i < (int)strlen(string));

                ret = asprintf(&expanded, "%.*s%s%s",
                    before_len, string, key, string + before_len + 1);
                if (ret < 0)
                        log_err(1, "asprintf");

                //log_debugx("\"%s\" expanded with key \"%s\" to \"%s\"",
                //    string, key, expanded);

                /*
                 * Figure out where to start searching for next variable.
                 */
                string = expanded;
                i = before_len + strlen(key);
                if (i == (int)strlen(string))
                        break;
                backslashed = false;
                //assert(i < (int)strlen(string));
        }

        return (expanded);
}

/*
 * Expand "&" in n_location.  If the key is NULL, try to use
 * key from map entries themselves.  Keep in mind that maps
 * consist of tho levels of node structures, the key is one
 * level up.
 *
 * Variant with NULL key is for "automount -LL".
 */
void
node_expand_ampersand(struct node *n, const char *key)
{
        struct node *child;

        if (n->n_location != NULL) {
                if (key == NULL) {
                        if (n->n_parent != NULL &&
                            strcmp(n->n_parent->n_key, "*") != 0) {
                                n->n_location = expand_ampersand(n->n_location,
                                    n->n_parent->n_key);
                        }
                } else {
                        n->n_location = expand_ampersand(n->n_location, key);
                }
        }

        TAILQ_FOREACH(child, &n->n_children, n_next)
                node_expand_ampersand(child, key);
}

/*
 * Expand "*" in n_key.
 */
void
node_expand_wildcard(struct node *n, const char *key)
{
        struct node *child, *expanded;

        assert(key != NULL);

        if (strcmp(n->n_key, "*") == 0) {
                expanded = node_duplicate(n, NULL);
                expanded->n_key = checked_strdup(key);
                node_move_after(expanded, n);
        }

        TAILQ_FOREACH(child, &n->n_children, n_next)
                node_expand_wildcard(child, key);
}

int
node_expand_defined(struct node *n)
{
        struct node *child;
        int error, cumulated_error = 0;

        if (n->n_location != NULL) {
                n->n_location = defined_expand(n->n_location);
                if (n->n_location == NULL) {
                        log_warnx("failed to expand location for %s",
                            node_path(n));
                        return (EINVAL);
                }
        }

        TAILQ_FOREACH(child, &n->n_children, n_next) {
                error = node_expand_defined(child);
                if (error != 0 && cumulated_error == 0)
                        cumulated_error = error;
        }

        return (cumulated_error);
}

static bool
node_is_direct_key(const struct node *n)
{

        if (n->n_parent != NULL && n->n_parent->n_parent == NULL &&
            strcmp(n->n_key, "/-") == 0) {
                return (true);
        }

        return (false);
}

bool
node_is_direct_map(const struct node *n)
{

        for (;;) {
                assert(n->n_parent != NULL);
                if (n->n_parent->n_parent == NULL)
                        break;
                n = n->n_parent;
        }

        return (node_is_direct_key(n));
}

bool
node_has_wildcards(const struct node *n)
{
        const struct node *child;

        TAILQ_FOREACH(child, &n->n_children, n_next) {
                if (strcmp(child->n_key, "*") == 0)
                        return (true);
        }

        return (false);
}

static void
node_expand_maps(struct node *n, bool indirect)
{
        struct node *child, *tmp;

        TAILQ_FOREACH_SAFE(child, &n->n_children, n_next, tmp) {
                if (node_is_direct_map(child)) {
                        if (indirect)
                                continue;
                } else {
                        if (indirect == false)
                                continue;
                }

                /*
                 * This is the first-level map node; the one that contains
                 * the key and subnodes with mountpoints and actual map names.
                 */
                if (child->n_map == NULL)
                        continue;

                if (indirect) {
                        log_debugx("map \"%s\" is an indirect map, parsing",
                            child->n_map);
                } else {
                        log_debugx("map \"%s\" is a direct map, parsing",
                            child->n_map);
                }
                parse_map(child, child->n_map, NULL, NULL);
        }
}

static void
node_expand_direct_maps(struct node *n)
{

        node_expand_maps(n, false);
}

void
node_expand_indirect_maps(struct node *n)
{

        node_expand_maps(n, true);
}

static char *
node_path_x(const struct node *n, char *x)
{
        char *path;

        if (n->n_parent == NULL)
                return (x);

        /*
         * Return "/-" for direct maps only if we were asked for path
         * to the "/-" node itself, not to any of its subnodes.
         */
        if (node_is_direct_key(n) && x[0] != '\0')
                return (x);

        assert(n->n_key[0] != '\0');
        path = concat(n->n_key, '/', x);
        free(x);

        return (node_path_x(n->n_parent, path));
}

/*
 * Return full path for node, consisting of concatenated
 * paths of node itself and all its parents, up to the root.
 */
char *
node_path(const struct node *n)
{
        char *path;
        size_t len;

        path = node_path_x(n, checked_strdup(""));

        /*
         * Strip trailing slash, unless the whole path is "/".
         */
        len = strlen(path);
        if (len > 1 && path[len - 1] == '/')
                path[len - 1] = '\0';

        return (path);
}

static char *
node_options_x(const struct node *n, char *x)
{
        char *options;

        if (n == NULL)
                return (x);

        options = concat(x, ',', n->n_options);
        free(x);

        return (node_options_x(n->n_parent, options));
}

/*
 * Return options for node, consisting of concatenated
 * options from the node itself and all its parents,
 * up to the root.
 */
char *
node_options(const struct node *n)
{

        return (node_options_x(n, checked_strdup("")));
}

static void
node_print_indent(const struct node *n, const char *cmdline_options,
    int indent)
{
        const struct node *child, *first_child;
        char *path, *options, *tmp;

        path = node_path(n);
        tmp = node_options(n);
        options = concat(cmdline_options, ',', tmp);
        free(tmp);

        /*
         * Do not show both parent and child node if they have the same
         * mountpoint; only show the child node.  This means the typical,
         * "key location", map entries are shown in a single line;
         * the "key mountpoint1 location2 mountpoint2 location2" entries
         * take multiple lines.
         */
        first_child = TAILQ_FIRST(&n->n_children);
        if (first_child == NULL || TAILQ_NEXT(first_child, n_next) != NULL ||
            strcmp(path, node_path(first_child)) != 0) {
                assert(n->n_location == NULL || n->n_map == NULL);
                printf("%*.s%-*s %s%-*s %-*s # %s map %s at %s:%d\n",
                    indent, "",
                    25 - indent,
                    path,
                    options[0] != '\0' ? "-" : " ",
                    20,
                    options[0] != '\0' ? options : "",
                    20,
                    n->n_location != NULL ? n->n_location : n->n_map != NULL ? n->n_map : "",
                    node_is_direct_map(n) ? "direct" : "indirect",
                    indent == 0 ? "referenced" : "defined",
                    n->n_config_file, n->n_config_line);
        }

        free(path);
        free(options);

        TAILQ_FOREACH(child, &n->n_children, n_next)
                node_print_indent(child, cmdline_options, indent + 2);
}

/*
 * Recursively print node with all its children.  The cmdline_options
 * argument is used for additional options to be prepended to all the
 * others - usually those are the options passed by command line.
 */
void
node_print(const struct node *n, const char *cmdline_options)
{
        const struct node *child;

        TAILQ_FOREACH(child, &n->n_children, n_next)
                node_print_indent(child, cmdline_options, 0);
}

static struct node *
node_find_x(struct node *node, const char *path)
{
        struct node *child, *found;
        char *tmp;
        size_t tmplen;

        //log_debugx("looking up %s in %s", path, node_path(node));

        if (!node_is_direct_key(node)) {
                tmp = node_path(node);
                tmplen = strlen(tmp);
                if (strncmp(tmp, path, tmplen) != 0) {
                        free(tmp);
                        return (NULL);
                }
                if (path[tmplen] != '/' && path[tmplen] != '\0') {
                        /*
                         * If we have two map entries like 'foo' and 'foobar', make
                         * sure the search for 'foobar' won't match 'foo' instead.
                         */
                        free(tmp);
                        return (NULL);
                }
                free(tmp);
        }

        TAILQ_FOREACH(child, &node->n_children, n_next) {
                found = node_find_x(child, path);
                if (found != NULL)
                        return (found);
        }

        if (node->n_parent == NULL || node_is_direct_key(node))
                return (NULL);

        return (node);
}

struct node *
node_find(struct node *root, const char *path)
{
        struct node *node;

        assert(root->n_parent == NULL);

        node = node_find_x(root, path);
        if (node != NULL)
                assert(node != root);

        return (node);
}

/*
 * Canonical form of a map entry looks like this:
 *
 * key [-options] [ [/mountpoint] [-options2] location ... ]
 *
 * Entries for executable maps are slightly different, as they
 * lack the 'key' field and are always single-line; the key field
 * for those maps is taken from 'executable_key' argument.
 *
 * We parse it in such a way that a map always has two levels - first
 * for key, and the second, for the mountpoint.
 */
static void
parse_map_yyin(struct node *parent, const char *map, const char *executable_key)
{
        char *key = NULL, *options = NULL, *mountpoint = NULL,
            *options2 = NULL, *location = NULL;
        int ret;
        struct node *node;

        lineno = 1;

        if (executable_key != NULL)
                key = checked_strdup(executable_key);

        for (;;) {
                ret = yylex();
                if (ret == 0 || ret == NEWLINE) {
                        /*
                         * In case of executable map, the key is always
                         * non-NULL, even if the map is empty.  So, make sure
                         * we don't fail empty maps here.
                         */
                        if ((key != NULL && executable_key == NULL) ||
                            options != NULL) {
                                log_errx(1, "truncated entry at %s, line %d",
                                    map, lineno);
                        }
                        if (ret == 0 || executable_key != NULL) {
                                /*
                                 * End of file.
                                 */
                                break;
                        } else {
                                key = options = NULL;
                                continue;
                        }
                }
                if (key == NULL) {
                        key = checked_strdup(yytext);
                        if (key[0] == '+') {
                                node_new(parent, key, NULL, NULL, map, lineno);
                                key = options = NULL;
                                continue;
                        }
                        continue;
                } else if (yytext[0] == '-') {
                        if (options != NULL) {
                                log_errx(1, "duplicated options at %s, line %d",
                                    map, lineno);
                        }
                        /*
                         * +1 to skip leading "-".
                         */
                        options = checked_strdup(yytext + 1);
                        continue;
                }

                /*
                 * We cannot properly handle a situation where the map key
                 * is "/".  Ignore such entries.
                 *
                 * XXX: According to Piete Brooks, Linux automounter uses
                 *      "/" as a wildcard character in LDAP maps.  Perhaps
                 *      we should work around this braindamage by substituting
                 *      "*" for "/"?
                 */
                if (strcmp(key, "/") == 0) {
                        log_warnx("nonsensical map key \"/\" at %s, line %d; "
                            "ignoring map entry ", map, lineno);

                        /*
                         * Skip the rest of the entry.
                         */
                        do {
                                ret = yylex();
                        } while (ret != 0 && ret != NEWLINE);

                        key = options = NULL;
                        continue;
                }

                //log_debugx("adding map node, %s", key);
                node = node_new(parent, key, options, NULL, map, lineno);
                key = options = NULL;

                for (;;) {
                        if (yytext[0] == '/') {
                                if (mountpoint != NULL) {
                                        log_errx(1, "duplicated mountpoint "
                                            "in %s, line %d", map, lineno);
                                }
                                if (options2 != NULL || location != NULL) {
                                        log_errx(1, "mountpoint out of order "
                                            "in %s, line %d", map, lineno);
                                }
                                mountpoint = checked_strdup(yytext);
                                goto again;
                        }

                        if (yytext[0] == '-') {
                                if (options2 != NULL) {
                                        log_errx(1, "duplicated options "
                                            "in %s, line %d", map, lineno);
                                }
                                if (location != NULL) {
                                        log_errx(1, "options out of order "
                                            "in %s, line %d", map, lineno);
                                }
                                options2 = checked_strdup(yytext + 1);
                                goto again;
                        }

                        if (location != NULL) {
                                log_errx(1, "too many arguments "
                                    "in %s, line %d", map, lineno);
                        }

                        /*
                         * If location field starts with colon, e.g. ":/dev/cd0",
                         * then strip it.
                         */
                        if (yytext[0] == ':') {
                                location = checked_strdup(yytext + 1);
                                if (location[0] == '\0') {
                                        log_errx(1, "empty location in %s, "
                                            "line %d", map, lineno);
                                }
                        } else {
                                location = checked_strdup(yytext);
                        }

                        if (mountpoint == NULL)
                                mountpoint = checked_strdup("/");
                        if (options2 == NULL)
                                options2 = checked_strdup("");

#if 0
                        log_debugx("adding map node, %s %s %s",
                            mountpoint, options2, location);
#endif
                        node_new(node, mountpoint, options2, location,
                            map, lineno);
                        mountpoint = options2 = location = NULL;
again:
                        ret = yylex();
                        if (ret == 0 || ret == NEWLINE) {
                                if (mountpoint != NULL || options2 != NULL ||
                                    location != NULL) {
                                        log_errx(1, "truncated entry "
                                            "in %s, line %d", map, lineno);
                                }
                                break;
                        }
                }
        }
}

/*
 * Parse output of a special map called without argument.  It is a list
 * of keys, separated by newlines.  They can contain whitespace, so use
 * getline(3) instead of lexer used for maps.
 */
static void
parse_map_keys_yyin(struct node *parent, const char *map)
{
        char *line = NULL, *key;
        size_t linecap = 0;
        ssize_t linelen;

        lineno = 1;

        for (;;) {
                linelen = getline(&line, &linecap, yyin);
                if (linelen < 0) {
                        /*
                         * End of file.
                         */
                        break;
                }
                if (linelen <= 1) {
                        /*
                         * Empty line, consisting of just the newline.
                         */
                        continue;
                }

                /*
                 * "-1" to strip the trailing newline.
                 */
                key = strndup(line, linelen - 1);

                log_debugx("adding key \"%s\"", key);
                node_new(parent, key, NULL, NULL, map, lineno);
                lineno++;
        }
        free(line);
}

static bool
file_is_executable(const char *path)
{
        struct stat sb;
        int error;

        error = stat(path, &sb);
        if (error != 0)
                log_err(1, "cannot stat %s", path);
        if ((sb.st_mode & S_IXUSR) || (sb.st_mode & S_IXGRP) ||
            (sb.st_mode & S_IXOTH))
                return (true);
        return (false);
}

/*
 * Parse a special map, e.g. "-hosts".
 */
static void
parse_special_map(struct node *parent, const char *map, const char *key)
{
        char *path;
        int error, ret;

        assert(map[0] == '-');

        /*
         * +1 to skip leading "-" in map name.
         */
        ret = asprintf(&path, "%s/special_%s", AUTO_SPECIAL_PREFIX, map + 1);
        if (ret < 0)
                log_err(1, "asprintf");

        yyin = auto_popen(path, key, NULL);
        assert(yyin != NULL);

        if (key == NULL) {
                parse_map_keys_yyin(parent, map);
        } else {
                parse_map_yyin(parent, map, key);
        }

        error = auto_pclose(yyin);
        yyin = NULL;
        if (error != 0)
                log_errx(1, "failed to handle special map \"%s\"", map);

        node_expand_includes(parent, false);
        node_expand_direct_maps(parent);

        free(path);
}

/*
 * Retrieve and parse map from directory services, e.g. LDAP.
 * Note that it is different from executable maps, in that
 * the include script outputs the whole map to standard output
 * (as opposed to executable maps that only output a single
 * entry, without the key), and it takes the map name as an
 * argument, instead of key.
 */
static void
parse_included_map(struct node *parent, const char *map)
{
        int error;

        assert(map[0] != '-');
        assert(map[0] != '/');

        error = access(AUTO_INCLUDE_PATH, F_OK);
        if (error != 0) {
                log_errx(1, "directory services not configured;"
                    " %s does not exist", AUTO_INCLUDE_PATH);
        }

        yyin = auto_popen(AUTO_INCLUDE_PATH, map, NULL);
        assert(yyin != NULL);

        parse_map_yyin(parent, map, NULL);

        error = auto_pclose(yyin);
        yyin = NULL;
        if (error != 0)
                log_errx(1, "failed to handle remote map \"%s\"", map);

        node_expand_includes(parent, false);
        node_expand_direct_maps(parent);
}

void
parse_map(struct node *parent, const char *map, const char *key,
    bool *wildcards)
{
        char *path = NULL;
        int error, ret;
        bool executable;

        assert(map != NULL);
        assert(map[0] != '\0');

        log_debugx("parsing map \"%s\"", map);

        if (wildcards != NULL)
                *wildcards = false;

        if (map[0] == '-') {
                if (wildcards != NULL)
                        *wildcards = true;
                return (parse_special_map(parent, map, key));
        }

        if (map[0] == '/') {
                path = checked_strdup(map);
        } else {
                ret = asprintf(&path, "%s/%s", AUTO_MAP_PREFIX, map);
                if (ret < 0)
                        log_err(1, "asprintf");
                log_debugx("map \"%s\" maps to \"%s\"", map, path);

                /*
                 * See if the file exists.  If not, try to obtain the map
                 * from directory services.
                 */
                error = access(path, F_OK);
                if (error != 0) {
                        log_debugx("map file \"%s\" does not exist; falling "
                            "back to directory services", path);
                        return (parse_included_map(parent, map));
                }
        }

        executable = file_is_executable(path);

        if (executable) {
                log_debugx("map \"%s\" is executable", map);

                if (wildcards != NULL)
                        *wildcards = true;

                if (key != NULL) {
                        yyin = auto_popen(path, key, NULL);
                } else {
                        yyin = auto_popen(path, NULL);
                }
                assert(yyin != NULL);
        } else {
                yyin = fopen(path, "r");
                if (yyin == NULL)
                        log_err(1, "unable to open \"%s\"", path);
        }

        free(path);
        path = NULL;

        parse_map_yyin(parent, map, executable ? key : NULL);

        if (executable) {
                error = auto_pclose(yyin);
                yyin = NULL;
                if (error != 0) {
                        log_errx(1, "failed to handle executable map \"%s\"",
                            map);
                }
        } else {
                fclose(yyin);
        }
        yyin = NULL;

        log_debugx("done parsing map \"%s\"", map);

        node_expand_includes(parent, false);
        node_expand_direct_maps(parent);
}

static void
parse_master_yyin(struct node *root, const char *master)
{
        char *mountpoint = NULL, *map = NULL, *options = NULL;
        int ret;

        /*
         * XXX: 1 gives incorrect values; wtf?
         */
        lineno = 0;

        for (;;) {
                ret = yylex();
                if (ret == 0 || ret == NEWLINE) {
                        if (mountpoint != NULL) {
                                //log_debugx("adding map for %s", mountpoint);
                                node_new_map(root, mountpoint, options, map,
                                    master, lineno);
                        }
                        if (ret == 0) {
                                break;
                        } else {
                                mountpoint = map = options = NULL;
                                continue;
                        }
                }
                if (mountpoint == NULL) {
                        mountpoint = checked_strdup(yytext);
                } else if (map == NULL) {
                        map = checked_strdup(yytext);
                } else if (options == NULL) {
                        /*
                         * +1 to skip leading "-".
                         */
                        options = checked_strdup(yytext + 1);
                } else {
                        log_errx(1, "too many arguments at %s, line %d",
                            master, lineno);
                }
        }
}

void
parse_master(struct node *root, const char *master)
{

        log_debugx("parsing auto_master file at \"%s\"", master);

        yyin = fopen(master, "r");
        if (yyin == NULL)
                err(1, "unable to open %s", master);

        parse_master_yyin(root, master);

        fclose(yyin);
        yyin = NULL;

        log_debugx("done parsing \"%s\"", master);

        node_expand_includes(root, true);
        node_expand_direct_maps(root);
}

/*
 * Two things daemon(3) does, that we actually also want to do
 * when running in foreground, is closing the stdin and chdiring
 * to "/".  This is what we do here.
 */
void
lesser_daemon(void)
{
        int error, fd;

        error = chdir("/");
        if (error != 0)
                log_warn("chdir");

        fd = open(_PATH_DEVNULL, O_RDWR, 0);
        if (fd < 0) {
                log_warn("cannot open %s", _PATH_DEVNULL);
                return;
        }

        error = dup2(fd, STDIN_FILENO);
        if (error != 0)
                log_warn("dup2");

        error = close(fd);
        if (error != 0) {
                /* Bloody hell. */
                log_warn("close");
        }
}

/*
 * Applicable to NFSv3 only, see rpc.umntall(8).
 */
void
rpc_umntall(void)
{
        FILE *f;

        f = auto_popen("rpc.umntall", "-k", NULL);
        assert(f != NULL);
        auto_pclose(f);
}

int
main(int argc, char **argv)
{
        char *cmdname;

        if (argv[0] == NULL)
                log_errx(1, "NULL command name");

        cmdname = basename(argv[0]);

        if (strcmp(cmdname, "automount") == 0)
                return (main_automount(argc, argv));
        else if (strcmp(cmdname, "automountd") == 0)
                return (main_automountd(argc, argv));
        else if (strcmp(cmdname, "autounmountd") == 0)
                return (main_autounmountd(argc, argv));
        else
                log_errx(1, "binary name should be either \"automount\", "
                    "\"automountd\", or \"autounmountd\"");
}