/* ----------------------------------------------------------------------- *
*
* lookup.c - API layer to implement nsswitch semantics for map reading
* and mount lookups.
*
* Copyright 2006 Ian Kent <raven@themaw.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, Inc., 675 Mass Ave, Cambridge MA 02139,
* USA; either version 2 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ----------------------------------------------------------------------- */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include "automount.h"
#include "nsswitch.h"
static void nsslist_cleanup(void *arg)
{
struct list_head *nsslist = (struct list_head *) arg;
if (!list_empty(nsslist))
free_sources(nsslist);
return;
}
static int do_read_master(struct master *master, char *type, time_t age)
{
struct lookup_mod *lookup;
const char *argv[2];
int argc;
int status;
argc = 1;
argv[0] = master->name;
argv[1] = NULL;
status = open_lookup(type, "", NULL, argc, argv, &lookup);
if (status != NSS_STATUS_SUCCESS)
return status;
status = lookup->lookup_read_master(master, age, lookup->context);
close_lookup(lookup);
return status;
}
static char *find_map_path(struct autofs_point *ap, struct map_source *map)
{
const char *mname = map->argv[0];
unsigned int mlen = strlen(mname);
char *tok, *ptr = NULL;
char *path = NULL;
char *search_path;
struct stat st;
/* Absolute path, just return a copy */
if (mname[0] == '/')
return strdup(mname);
/*
* This is different to the way it is in amd.
* autofs will always try to locate maps in AUTOFS_MAP_DIR
* but amd has no default and will not find a file map that
* isn't a full path when no search_path is configured, either
* in the mount point or global configuration.
*/
search_path = strdup(AUTOFS_MAP_DIR);
if (map->flags & MAP_FLAG_FORMAT_AMD) {
struct autofs_point *pap = ap;
char *tmp;
/*
* Make sure we get search_path from the root of the
* mount tree, if one is present in the configuration.
* Again different from amd, which ignores the submount
* case.
*/
while (pap->parent)
pap = pap->parent;
tmp = conf_amd_get_search_path(pap->path);
if (tmp) {
if (search_path)
free(search_path);
search_path = tmp;
}
}
if (!search_path)
return NULL;
tok = strtok_r(search_path, ":", &ptr);
while (tok) {
char *this = malloc(strlen(tok) + mlen + 2);
if (!this) {
free(search_path);
return NULL;
}
strcpy(this, tok);
strcat(this, "/");
strcat(this, mname);
if (!stat(this, &st)) {
path = this;
break;
}
free(this);
tok = strtok_r(NULL, ":", &ptr);
}
free(search_path);
return path;
}
static int read_master_map(struct master *master, char *type, time_t age)
{
unsigned int logopt = master->logopt;
char *path, *save_name;
int result;
if (strcasecmp(type, "files")) {
return do_read_master(master, type, age);
}
/*
* This is a special case as we need to append the
* normal location to the map name.
* note: It's invalid to specify a relative path.
*/
if (strchr(master->name, '/')) {
error(logopt, "relative path invalid in files map name");
return NSS_STATUS_NOTFOUND;
}
path = malloc(strlen(AUTOFS_MAP_DIR) + strlen(master->name) + 2);
if (!path)
return NSS_STATUS_UNKNOWN;
strcpy(path, AUTOFS_MAP_DIR);
strcat(path, "/");
strcat(path, master->name);
save_name = master->name;
master->name = path;
result = do_read_master(master, type, age);
master->name = save_name;
free(path);
return result;
}
int lookup_nss_read_master(struct master *master, time_t age)
{
unsigned int logopt = master->logopt;
struct list_head nsslist;
struct list_head *head, *p;
int result = NSS_STATUS_UNKNOWN;
/* If it starts with a '/' it has to be a file or LDAP map */
if (*master->name == '/') {
if (*(master->name + 1) == '/') {
debug(logopt, "reading master ldap %s", master->name);
result = do_read_master(master, "ldap", age);
} else {
debug(logopt, "reading master file %s", master->name);
result = do_read_master(master, "file", age);
}
if (result == NSS_STATUS_UNAVAIL)
master->read_fail = 1;
return result;
} else {
char *name = master->name;
char *tmp;
/* Old style name specification will remain I think. */
tmp = strchr(name, ':');
if (tmp) {
char source[10];
memset(source, 0, 10);
if ((!strncmp(name, "file", 4) &&
(name[4] == ',' || name[4] == ':')) ||
(!strncmp(name, "yp", 2) &&
(name[2] == ',' || name[2] == ':')) ||
(!strncmp(name, "nis", 3) &&
(name[3] == ',' || name[3] == ':')) ||
(!strncmp(name, "nisplus", 7) &&
(name[7] == ',' || name[7] == ':')) ||
(!strncmp(name, "ldap", 4) &&
(name[4] == ',' || name[4] == ':')) ||
(!strncmp(name, "ldaps", 5) &&
(name[5] == ',' || name[5] == ':')) ||
(!strncmp(name, "sss", 3) ||
(name[3] == ',' || name[3] == ':')) ||
(!strncmp(name, "dir", 3) &&
(name[3] == ',' || name[3] == ':'))) {
strncpy(source, name, tmp - name);
/*
* If it's an ldap map leave the source in the
* name so the lookup module can work out if
* ldaps has been requested.
*/
if (strncmp(name, "ldap", 4)) {
master->name = tmp + 1;
debug(logopt, "reading master %s %s",
source, master->name);
} else {
master->name = name;
debug(logopt, "reading master %s %s",
source, tmp + 1);
}
result = do_read_master(master, source, age);
master->name = name;
if (result == NSS_STATUS_UNAVAIL)
master->read_fail = 1;
return result;
}
}
}
INIT_LIST_HEAD(&nsslist);
result = nsswitch_parse(&nsslist);
if (result) {
if (!list_empty(&nsslist))
free_sources(&nsslist);
error(logopt, "can't to read name service switch config.");
return NSS_STATUS_UNAVAIL;
}
/* First one gets it */
result = NSS_STATUS_SUCCESS;
head = &nsslist;
list_for_each(p, head) {
struct nss_source *this;
int status;
this = list_entry(p, struct nss_source, list);
if (strncmp(this->source, "files", 5) &&
strncmp(this->source, "nis", 3) &&
strncmp(this->source, "nisplus", 7) &&
strncmp(this->source, "ldap", 4) &&
strncmp(this->source, "sss", 3))
continue;
debug(logopt,
"reading master %s %s", this->source, master->name);
result = read_master_map(master, this->source, age);
/*
* If the name of the master map hasn't been explicitly
* configured and we're not reading an included master map
* then we're using auto.master as the default. Many setups
* also use auto_master as the default master map so we
* check for this map when auto.master isn't found.
*/
if (result != NSS_STATUS_SUCCESS &&
!master->depth && !defaults_master_set()) {
char *tmp = strchr(master->name, '.');
if (tmp) {
debug(logopt,
"%s not found, replacing '.' with '_'",
master->name);
*tmp = '_';
result = read_master_map(master, this->source, age);
if (result != NSS_STATUS_SUCCESS)
*tmp = '.';
}
}
/* We've been instructed to move onto the next source */
if (result == NSS_STATUS_TRYAGAIN) {
result = NSS_STATUS_SUCCESS;
continue;
}
if (result == NSS_STATUS_UNKNOWN ||
result == NSS_STATUS_NOTFOUND) {
debug(logopt, "no map - continuing to next source");
result = NSS_STATUS_SUCCESS;
continue;
}
if (result == NSS_STATUS_UNAVAIL)
master->read_fail = 1;
status = check_nss_result(this, result);
if (status >= 0)
break;
}
if (!list_empty(&nsslist))
free_sources(&nsslist);
return result;
}
static int do_read_map(struct autofs_point *ap, struct map_source *map, time_t age)
{
struct lookup_mod *lookup;
int status;
lookup = NULL;
master_source_writelock(ap->entry);
if (!map->lookup) {
status = open_lookup(map->type, "", map->format,
map->argc, map->argv, &lookup);
if (status != NSS_STATUS_SUCCESS) {
master_source_unlock(ap->entry);
debug(ap->logopt,
"lookup module %s open failed", map->type);
return status;
}
map->lookup = lookup;
} else {
lookup = map->lookup;
status = lookup->lookup_reinit(map->format,
map->argc, map->argv,
&lookup->context);
if (status)
warn(ap->logopt,
"lookup module %s reinit failed", map->type);
}
master_source_unlock(ap->entry);
if (!map->stale)
return NSS_STATUS_SUCCESS;
master_source_current_wait(ap->entry);
ap->entry->current = map;
status = lookup->lookup_read_map(ap, age, lookup->context);
if (status != NSS_STATUS_SUCCESS)
map->stale = 0;
/*
* For maps that don't support enumeration return success
* and do whatever we must to have autofs function with an
* empty map entry cache.
*
* For indirect maps that use the browse option, when the
* server is unavailable continue as best we can with
* whatever we have in the cache, if anything.
*/
if (status == NSS_STATUS_UNKNOWN ||
(ap->type == LKP_INDIRECT && status == NSS_STATUS_UNAVAIL))
return NSS_STATUS_SUCCESS;
return status;
}
static int read_file_source_instance(struct autofs_point *ap, struct map_source *map, time_t age)
{
struct map_source *instance;
char src_file[] = "file";
char src_prog[] = "program";
struct stat st;
char *type, *format;
char *path;
if (map->argc < 1) {
error(ap->logopt, "invalid arguments for autofs_point");
return NSS_STATUS_UNKNOWN;
}
path = find_map_path(ap, map);
if (!path)
return NSS_STATUS_UNKNOWN;
if (stat(path, &st) == -1) {
warn(ap->logopt, "file map %s not found", path);
free(path);
return NSS_STATUS_NOTFOUND;
}
if (!S_ISREG(st.st_mode)) {
free(path);
return NSS_STATUS_NOTFOUND;
}
if (st.st_mode & __S_IEXEC)
type = src_prog;
else
type = src_file;
format = map->format;
instance = master_find_source_instance(map, type, format, 0, NULL);
if (!instance) {
const char **argv;
int argc;
argc = map->argc;
argv = copy_argv(map->argc, map->argv);
if (!argv) {
error(ap->logopt, "failed to copy args");
free(path);
return NSS_STATUS_UNKNOWN;
}
if (argv[0])
free((char *) argv[0]);
argv[0] = path;
path = NULL;
instance = master_add_source_instance(map, type, format, age, argc, argv);
free_argv(argc, argv);
if (!instance)
return NSS_STATUS_UNAVAIL;
instance->recurse = map->recurse;
instance->depth = map->depth;
}
instance->stale = map->stale;
if (path)
free(path);
return do_read_map(ap, instance, age);
}
static int read_source_instance(struct autofs_point *ap, struct map_source *map, const char *type, time_t age)
{
struct map_source *instance;
const char *format;
format = map->format;
instance = master_find_source_instance(map, type, format, 0, NULL);
if (!instance) {
int argc = map->argc;
const char **argv = map->argv;
instance = master_add_source_instance(map, type, format, age, argc, argv);
if (!instance)
return NSS_STATUS_UNAVAIL;
instance->recurse = map->recurse;
instance->depth = map->depth;
}
instance->stale = map->stale;
return do_read_map(ap, instance, age);
}
static void argv_cleanup(void *arg)
{
struct map_source *tmap = (struct map_source *) arg;
/* path is freed in free_argv */
free_argv(tmap->argc, tmap->argv);
return;
}
static int lookup_map_read_map(struct autofs_point *ap,
struct map_source *map, time_t age)
{
char *path;
if (!map->argv[0]) {
if (!strcmp(map->type, "hosts"))
return do_read_map(ap, map, age);
return NSS_STATUS_UNKNOWN;
}
/*
* This is only called when map->type != NULL.
* We only need to look for a map if source type is
* file and the map name doesn't begin with a "/".
*/
if (strncmp(map->type, "file", 4))
return do_read_map(ap, map, age);
if (map->argv[0][0] == '/')
return do_read_map(ap, map, age);
path = find_map_path(ap, map);
if (!path)
return NSS_STATUS_UNKNOWN;
if (map->argc >= 1) {
if (map->argv[0])
free((char *) map->argv[0]);
map->argv[0] = path;
} else {
error(ap->logopt, "invalid arguments for autofs_point");
free(path);
return NSS_STATUS_UNKNOWN;
}
return do_read_map(ap, map, age);
}
static enum nsswitch_status read_map_source(struct nss_source *this,
struct autofs_point *ap, struct map_source *map, time_t age)
{
if (strcasecmp(this->source, "files")) {
return read_source_instance(ap, map, this->source, age);
}
/*
* This is a special case as we need to append the
* normal location to the map name.
* note: It's invalid to specify a relative path.
*/
if (strchr(map->argv[0], '/')) {
error(ap->logopt, "relative path invalid in files map name");
return NSS_STATUS_NOTFOUND;
}
return read_file_source_instance(ap, map, age);
}
int lookup_nss_read_map(struct autofs_point *ap, struct map_source *source, time_t age)
{
struct master_mapent *entry = ap->entry;
struct list_head nsslist;
struct list_head *head, *p;
struct nss_source *this;
struct map_source *map;
enum nsswitch_status status;
unsigned int at_least_one = 0;
int result = 0;
/*
* For each map source (ie. each entry for the mount
* point in the master map) do the nss lookup to
* locate the map and read it.
*/
if (source)
map = source;
else
map = entry->maps;
while (map) {
/* Is map source up to date or no longer valid */
if (!map->stale || entry->age > map->age) {
map = map->next;
continue;
}
if (map->type) {
if (!strncmp(map->type, "multi", 5))
debug(ap->logopt, "reading multi map");
else
debug(ap->logopt,
"reading map %s %s",
map->type, map->argv[0]);
result = lookup_map_read_map(ap, map, age);
map = map->next;
continue;
}
/* If it starts with a '/' it has to be a file or LDAP map */
if (map->argv && *map->argv[0] == '/') {
if (*(map->argv[0] + 1) == '/') {
char *tmp = strdup("ldap");
if (!tmp) {
map = map->next;
continue;
}
map->type = tmp;
debug(ap->logopt,
"reading map %s %s", tmp, map->argv[0]);
result = do_read_map(ap, map, age);
} else {
debug(ap->logopt,
"reading map file %s", map->argv[0]);
result = read_file_source_instance(ap, map, age);
}
map = map->next;
continue;
}
INIT_LIST_HEAD(&nsslist);
pthread_cleanup_push(nsslist_cleanup, &nsslist);
status = nsswitch_parse(&nsslist);
pthread_cleanup_pop(0);
if (status) {
error(ap->logopt,
"can't to read name service switch config.");
result = 1;
break;
}
pthread_cleanup_push(nsslist_cleanup, &nsslist);
head = &nsslist;
list_for_each(p, head) {
this = list_entry(p, struct nss_source, list);
if (map->flags & MAP_FLAG_FORMAT_AMD &&
!strcmp(this->source, "sss")) {
warn(ap->logopt,
"source sss is not available for amd maps.");
continue;
}
debug(ap->logopt,
"reading map %s %s", this->source, map->argv[0]);
result = read_map_source(this, ap, map, age);
if (result == NSS_STATUS_UNKNOWN)
continue;
/* Try to avoid updating the map cache if an instance
* is unavailable */
if (result == NSS_STATUS_UNAVAIL)
map->stale = 0;
if (result == NSS_STATUS_SUCCESS) {
at_least_one = 1;
result = NSS_STATUS_TRYAGAIN;
}
status = check_nss_result(this, result);
if (status >= 0) {
map = NULL;
break;
}
result = NSS_STATUS_SUCCESS;
}
pthread_cleanup_pop(1);
if (!map)
break;
map = map->next;
}
if (!result || at_least_one)
return 1;
return 0;
}
static char *make_browse_path(unsigned int logopt,
const char *root, const char *key,
const char *prefix)
{
unsigned int l_prefix;
unsigned int k_len, r_len;
char *k_start;
char *path;
k_start = (char *) key;
k_len = strlen(key);
l_prefix = 0;
if (prefix) {
l_prefix = strlen(prefix);
if (l_prefix > k_len)
return NULL;
/* If the prefix doesn't match the beginning
* of the key this entry isn't a sub directory
* at this level.
*/
if (strncmp(key, prefix, l_prefix))
return NULL;
/* Directory entry starts following the prefix */
k_start += l_prefix;
}
/* No remaining "/" allowed here */
if (strchr(k_start, '/'))
return NULL;
r_len = strlen(root);
if ((r_len + strlen(k_start)) > KEY_MAX_LEN)
return NULL;
path = malloc(r_len + k_len + 2);
if (!path) {
warn(logopt, "failed to allocate full path");
return NULL;
}
sprintf(path, "%s/%s", root, k_start);
return path;
}
int lookup_ghost(struct autofs_point *ap, const char *root)
{
struct master_mapent *entry = ap->entry;
struct map_source *map;
struct mapent_cache *mc;
struct mapent *me;
char buf[MAX_ERR_BUF];
struct stat st;
char *fullpath;
int ret;
if (!strcmp(ap->path, "/-"))
return LKP_FAIL | LKP_DIRECT;
if (!(ap->flags & MOUNT_FLAG_GHOST))
return LKP_INDIRECT;
pthread_cleanup_push(master_source_lock_cleanup, entry);
master_source_readlock(entry);
map = entry->maps;
while (map) {
/*
* Only consider map sources that have been read since
* the map entry was last updated.
*/
if (entry->age > map->age) {
map = map->next;
continue;
}
mc = map->mc;
pthread_cleanup_push(cache_lock_cleanup, mc);
cache_readlock(mc);
me = cache_enumerate(mc, NULL);
while (me) {
/*
* Map entries that have been created in the cache
* due to a negative lookup shouldn't have directories
* created if they haven't already been created.
*/
if (!me->mapent)
goto next;
/* Wildcard cannot be a browse directory and amd map
* keys may end with the wildcard.
*/
if (strchr(me->key, '*'))
goto next;
/* This will also take care of amd "/defaults" entry as
* amd map keys are not allowd to start with "/"
*/
if (*me->key == '/') {
if (map->flags & MAP_FLAG_FORMAT_AMD)
goto next;
/* It's a busy multi-mount - leave till next time */
if (list_empty(&me->multi_list))
error(ap->logopt,
"invalid key %s", me->key);
goto next;
}
fullpath = make_browse_path(ap->logopt,
root, me->key, ap->pref);
if (!fullpath)
goto next;
ret = stat(fullpath, &st);
if (ret == -1 && errno != ENOENT) {
char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
warn(ap->logopt, "stat error %s", estr);
free(fullpath);
goto next;
}
/* Directory already exists? */
if (!ret) {
free(fullpath);
goto next;
}
ret = mkdir_path(fullpath, mp_mode);
if (ret < 0 && errno != EEXIST) {
char *estr = strerror_r(errno, buf, MAX_ERR_BUF);
warn(ap->logopt,
"mkdir_path %s failed: %s", fullpath, estr);
free(fullpath);
goto next;
}
if (stat(fullpath, &st) != -1) {
me->dev = st.st_dev;
me->ino = st.st_ino;
}
free(fullpath);
next:
me = cache_enumerate(mc, me);
}
pthread_cleanup_pop(1);
map = map->next;
}
pthread_cleanup_pop(1);
return LKP_INDIRECT;
}
int do_lookup_mount(struct autofs_point *ap, struct map_source *map, const char *name, int name_len)
{
struct lookup_mod *lookup;
int status;
if (!map->lookup) {
status = open_lookup(map->type, "",
map->format, map->argc, map->argv, &lookup);
if (status != NSS_STATUS_SUCCESS) {
debug(ap->logopt,
"lookup module %s open failed", map->type);
return status;
}
map->lookup = lookup;
}
lookup = map->lookup;
master_source_current_wait(ap->entry);
ap->entry->current = map;
status = lookup->lookup_mount(ap, name, name_len, lookup->context);
return status;
}
static int lookup_amd_instance(struct autofs_point *ap,
struct map_source *map,
const char *name, int name_len)
{
struct map_source *instance;
struct amd_entry *entry;
const char *argv[2];
const char **pargv = NULL;
int argc = 0;
struct mapent *me;
char *m_key;
me = cache_lookup_distinct(map->mc, name);
if (!me || !me->multi) {
error(ap->logopt, "expected multi mount entry not found");
return NSS_STATUS_UNKNOWN;
}
m_key = malloc(strlen(ap->path) + strlen(me->multi->key) + 2);
if (!m_key) {
error(ap->logopt,
"failed to allocate storage for search key");
return NSS_STATUS_UNKNOWN;
}
strcpy(m_key, ap->path);
strcat(m_key, "/");
strcat(m_key, me->multi->key);
entry = master_find_amdmount(ap, m_key);
free(m_key);
if (!entry) {
error(ap->logopt, "expected amd mount entry not found");
return NSS_STATUS_UNKNOWN;
}
if (strcmp(entry->type, "host")) {
error(ap->logopt, "unexpected map type %s", entry->type);
return NSS_STATUS_UNKNOWN;
}
if (entry->opts && *entry->opts) {
argv[0] = entry->opts;
argv[1] = NULL;
pargv = argv;
argc = 1;
}
instance = master_find_source_instance(map, "hosts", "sun", argc, pargv);
/* If this is an nss map instance it may have an amd host map sub instance */
if (!instance && map->instance) {
struct map_source *next = map->instance;
while (next) {
instance = master_find_source_instance(next,
"hosts", "sun", argc, pargv);
if (instance)
break;
next = next->next;
}
}
if (!instance) {
error(ap->logopt, "expected hosts map instance not found");
return NSS_STATUS_UNKNOWN;
}
return do_lookup_mount(ap, instance, name, name_len);
}
static int lookup_name_file_source_instance(struct autofs_point *ap, struct map_source *map, const char *name, int name_len)
{
struct map_source *instance;
char src_file[] = "file";
char src_prog[] = "program";
time_t age = monotonic_time(NULL);
struct stat st;
char *type, *format;
char *path;
if (*name == '/' && map->flags & MAP_FLAG_FORMAT_AMD)
return lookup_amd_instance(ap, map, name, name_len);
if (map->argc < 1) {
error(ap->logopt, "invalid arguments for autofs_point");
return NSS_STATUS_UNKNOWN;
}
path = find_map_path(ap, map);
if (!path)
return NSS_STATUS_UNKNOWN;
if (stat(path, &st) == -1) {
debug(ap->logopt, "file map not found");
free(path);
return NSS_STATUS_NOTFOUND;
}
if (!S_ISREG(st.st_mode)) {
free(path);
return NSS_STATUS_NOTFOUND;
}
if (st.st_mode & __S_IEXEC)
type = src_prog;
else
type = src_file;
format = map->format;
instance = master_find_source_instance(map, type, format, 0, NULL);
if (!instance) {
const char **argv;
int argc;
argc = map->argc;
argv = copy_argv(map->argc, map->argv);
if (!argv) {
error(ap->logopt, "failed to copy args");
free(path);
return NSS_STATUS_UNKNOWN;
}
if (argv[0])
free((char *) argv[0]);
argv[0] = path;
path = NULL;
instance = master_add_source_instance(map, type, format, age, argc, argv);
free_argv(argc, argv);
if (!instance)
return NSS_STATUS_NOTFOUND;
instance->recurse = map->recurse;
instance->depth = map->depth;
}
if (path)
free(path);
return do_lookup_mount(ap, instance, name, name_len);
}
static int lookup_name_source_instance(struct autofs_point *ap, struct map_source *map, const char *type, const char *name, int name_len)
{
struct map_source *instance;
const char *format;
time_t age = monotonic_time(NULL);
if (*name == '/' && map->flags & MAP_FLAG_FORMAT_AMD)
return lookup_amd_instance(ap, map, name, name_len);
format = map->format;
instance = master_find_source_instance(map, type, format, 0, NULL);
if (!instance) {
int argc = map->argc;
const char **argv = map->argv;
instance = master_add_source_instance(map, type, format, age, argc, argv);
if (!instance)
return NSS_STATUS_NOTFOUND;
instance->recurse = map->recurse;
instance->depth = map->depth;
}
return do_lookup_mount(ap, instance, name, name_len);
}
static int do_name_lookup_mount(struct autofs_point *ap,
struct map_source *map,
const char *name, int name_len)
{
char *path;
if (!map->argv[0]) {
if (!strcmp(map->type, "hosts"))
return do_lookup_mount(ap, map, name, name_len);
return NSS_STATUS_UNKNOWN;
}
if (*name == '/' && map->flags & MAP_FLAG_FORMAT_AMD)
return lookup_amd_instance(ap, map, name, name_len);
/*
* This is only called when map->type != NULL.
* We only need to look for a map if source type is
* file and the map name doesn't begin with a "/".
*/
if (strncmp(map->type, "file", 4))
return do_lookup_mount(ap, map, name, name_len);
if (map->argv[0][0] == '/')
return do_lookup_mount(ap, map, name, name_len);
path = find_map_path(ap, map);
if (!path)
return NSS_STATUS_UNKNOWN;
if (map->argc >= 1) {
if (map->argv[0])
free((char *) map->argv[0]);
map->argv[0] = path;
} else {
error(ap->logopt, "invalid arguments for autofs_point");
free(path);
return NSS_STATUS_UNKNOWN;
}
return do_lookup_mount(ap, map, name, name_len);
}
static enum nsswitch_status lookup_map_name(struct nss_source *this,
struct autofs_point *ap, struct map_source *map,
const char *name, int name_len)
{
if (strcasecmp(this->source, "files"))
return lookup_name_source_instance(ap, map,
this->source, name, name_len);
/*
* autofs build-in map for nsswitch "files" is "file".
* This is a special case as we need to append the
* normal location to the map name.
* note: we consider it invalid to specify a relative
* path.
*/
if (strchr(map->argv[0], '/')) {
error(ap->logopt, "relative path invalid in files map name");
return NSS_STATUS_NOTFOUND;
}
return lookup_name_file_source_instance(ap, map, name, name_len);
}
static struct map_source *lookup_get_map_source(struct master_mapent *entry)
{
struct map_source *map = entry->maps;
struct stat st;
char *type;
if (map->type || *map->argv[0] != '/')
return map;
if (*(map->argv[0] + 1) == '/')
return map;
if (stat(map->argv[0], &st) == -1)
return NULL;
if (!S_ISREG(st.st_mode))
return NULL;
if (st.st_mode & __S_IEXEC)
type = "program";
else
type = "file";
/* This is a file source with a path starting with "/".
* But file maps can be either plain text or executable
* so they use a map instance and the actual map source
* remains untouched.
*/
return master_find_source_instance(map, type, map->format, 0, NULL);
}
static void update_negative_cache(struct autofs_point *ap, struct map_source *source, const char *name)
{
struct master_mapent *entry = ap->entry;
struct map_source *map;
struct mapent *me;
/* Don't update negative cache for included maps */
if (source && source->depth)
return;
/* Don't update the wildcard */
if (strlen(name) == 1 && *name == '*')
return;
/* Have we recorded the lookup fail for negative caching? */
me = lookup_source_mapent(ap, name, LKP_DISTINCT);
if (me)
/*
* Already exists in the cache, the mount fail updates
* will update negative timeout status.
*/
cache_unlock(me->mc);
else {
if (!defaults_disable_not_found_message()) {
/* This really should be a warning but the original
* request for this needed it to be unconditional.
* That produces, IMHO, unnecessary noise in the log
* so a configuration option has been added to provide
* the ability to turn it off.
*/
logmsg("key \"%s\" not found in map source(s).", name);
}
/* Doesn't exist in any source, just add it somewhere.
* Also take care to use the same map source used by
* map reads and key lookups for the update.
*/
if (source)
map = source;
else
map = lookup_get_map_source(entry);
if (map) {
time_t now = monotonic_time(NULL);
int rv = CHE_FAIL;
cache_writelock(map->mc);
me = cache_lookup_distinct(map->mc, name);
if (me)
rv = cache_push_mapent(me, NULL);
else
rv = cache_update(map->mc, map, name, NULL, now);
if (rv != CHE_FAIL) {
me = cache_lookup_distinct(map->mc, name);
if (me)
me->status = now + ap->negative_timeout;
}
cache_unlock(map->mc);
}
}
return;
}
int lookup_nss_mount(struct autofs_point *ap, struct map_source *source, const char *name, int name_len)
{
struct master_mapent *entry = ap->entry;
struct list_head nsslist;
struct list_head *head, *p;
struct nss_source *this;
struct map_source *map;
enum nsswitch_status status;
int result = NSS_STATUS_UNKNOWN;
/*
* For each map source (ie. each entry for the mount
* point in the master map) do the nss lookup to
* locate the map and lookup the name.
*/
pthread_cleanup_push(master_source_lock_cleanup, entry);
master_source_readlock(entry);
if (source)
map = source;
else
map = entry->maps;
while (map) {
/*
* Only consider map sources that have been read since
* the map entry was last updated.
*/
if (entry->age > map->age) {
status = NSS_STATUS_UNAVAIL;
map = map->next;
continue;
}
sched_yield();
if (map->type) {
result = do_name_lookup_mount(ap, map, name, name_len);
if (result == NSS_STATUS_SUCCESS)
break;
map = map->next;
continue;
}
/* If it starts with a '/' it has to be a file or LDAP map */
if (*map->argv[0] == '/') {
if (*(map->argv[0] + 1) == '/') {
char *tmp = strdup("ldap");
if (!tmp) {
map = map->next;
status = NSS_STATUS_TRYAGAIN;
continue;
}
map->type = tmp;
result = do_lookup_mount(ap, map, name, name_len);
} else
result = lookup_name_file_source_instance(ap, map, name, name_len);
if (result == NSS_STATUS_SUCCESS)
break;
map = map->next;
continue;
}
INIT_LIST_HEAD(&nsslist);
status = nsswitch_parse(&nsslist);
if (status) {
error(ap->logopt,
"can't to read name service switch config.");
result = 1;
break;
}
head = &nsslist;
list_for_each(p, head) {
this = list_entry(p, struct nss_source, list);
if (map->flags & MAP_FLAG_FORMAT_AMD &&
!strcmp(this->source, "sss")) {
warn(ap->logopt,
"source sss is not available for amd maps.");
result = NSS_STATUS_UNAVAIL;
continue;
}
result = lookup_map_name(this, ap, map, name, name_len);
if (result == NSS_STATUS_UNKNOWN)
continue;
status = check_nss_result(this, result);
if (status >= 0) {
map = NULL;
break;
}
}
if (!list_empty(&nsslist))
free_sources(&nsslist);
if (!map)
break;
map = map->next;
}
if (ap->state != ST_INIT)
send_map_update_request(ap);
/*
* The last source lookup will return NSS_STATUS_NOTFOUND if the
* map exits and the key has not been found but the map may also
* not exist in which case the key is also not found.
*/
if (result == NSS_STATUS_NOTFOUND || result == NSS_STATUS_UNAVAIL)
update_negative_cache(ap, source, name);
pthread_cleanup_pop(1);
return !result;
}
static void lookup_close_lookup_instances(struct map_source *map)
{
struct map_source *instance;
instance = map->instance;
while (instance) {
lookup_close_lookup_instances(instance);
instance = instance->next;
}
if (map->lookup) {
close_lookup(map->lookup);
map->lookup = NULL;
}
}
void lookup_close_lookup(struct autofs_point *ap)
{
struct map_source *map;
map = ap->entry->maps;
if (!map)
return;
while (map) {
lookup_close_lookup_instances(map);
map = map->next;
}
return;
}
static char *make_fullpath(struct autofs_point *ap, const char *key)
{
char *path = NULL;
int l;
if (*key != '/')
path = make_browse_path(ap->logopt, ap->path, key, ap->pref);
else {
l = strlen(key) + 1;
if (l > KEY_MAX_LEN)
goto out;
path = malloc(l);
if (!path)
goto out;
strcpy(path, key);
}
out:
return path;
}
void lookup_prune_one_cache(struct autofs_point *ap, struct mapent_cache *mc, time_t age)
{
struct mapent *me, *this;
char *path;
int status = CHE_FAIL;
me = cache_enumerate(mc, NULL);
while (me) {
struct mapent *valid;
char *key = NULL, *next_key = NULL;
if (me->age >= age) {
/*
* Reset time of last fail for valid map entries to
* force entry update and subsequent mount retry.
* A map entry that's still invalid after a read
* may have been created by a failed wildcard lookup
* so reset the status on those too.
*/
if (me->mapent || cache_lookup(mc, "*"))
me->status = 0;
me = cache_enumerate(mc, me);
continue;
}
key = strdup(me->key);
me = cache_enumerate(mc, me);
/* Don't consider any entries with a wildcard */
if (!key || strchr(key, '*')) {
if (key)
free(key);
continue;
}
path = make_fullpath(ap, key);
if (!path) {
warn(ap->logopt, "can't malloc storage for path");
free(key);
continue;
}
/*
* If this key has another valid entry we want to prune it,
* even if it's a mount, as the valid entry will take the
* mount if it is a direct mount or it's just a stale indirect
* cache entry.
*/
valid = lookup_source_valid_mapent(ap, key, LKP_DISTINCT);
if (valid && valid->mc == mc) {
/*
* We've found a map entry that has been removed from
* the current cache so it isn't really valid. Set the
* mapent negative to prevent further mount requests
* using the cache entry.
*/
debug(ap->logopt, "removed map entry detected, mark negative");
if (valid->mapent) {
free(valid->mapent);
valid->mapent = NULL;
}
cache_unlock(valid->mc);
valid = NULL;
}
if (!valid &&
is_mounted(path, MNTS_REAL)) {
debug(ap->logopt, "prune posponed, %s mounted", path);
free(key);
free(path);
continue;
}
if (valid)
cache_unlock(valid->mc);
if (me)
next_key = strdup(me->key);
cache_unlock(mc);
cache_writelock(mc);
this = cache_lookup_distinct(mc, key);
if (!this) {
cache_unlock(mc);
goto next;
}
if (valid)
cache_delete(mc, key);
else if (!is_mounted(path, MNTS_AUTOFS)) {
dev_t devid = ap->dev;
status = CHE_FAIL;
if (ap->type == LKP_DIRECT)
devid = this->dev;
if (this->ioctlfd == -1)
status = cache_delete(mc, key);
if (status != CHE_FAIL) {
if (ap->type == LKP_INDIRECT) {
if (ap->flags & MOUNT_FLAG_GHOST)
rmdir_path(ap, path, devid);
} else
rmdir_path(ap, path, devid);
}
}
cache_unlock(mc);
next:
cache_readlock(mc);
if (next_key) {
me = cache_lookup_distinct(mc, next_key);
free(next_key);
}
free(key);
free(path);
}
return;
}
int lookup_prune_cache(struct autofs_point *ap, time_t age)
{
struct master_mapent *entry = ap->entry;
struct map_source *map;
pthread_cleanup_push(master_source_lock_cleanup, entry);
master_source_readlock(entry);
map = entry->maps;
while (map) {
/* Is the map stale */
if (!map->stale && !check_stale_instances(map)) {
map = map->next;
continue;
}
pthread_cleanup_push(cache_lock_cleanup, map->mc);
cache_readlock(map->mc);
lookup_prune_one_cache(ap, map->mc, age);
pthread_cleanup_pop(1);
clear_stale_instances(map);
map->stale = 0;
map = map->next;
}
pthread_cleanup_pop(1);
return 1;
}
/* Return with cache readlock held */
struct mapent *lookup_source_valid_mapent(struct autofs_point *ap, const char *key, unsigned int type)
{
struct master_mapent *entry = ap->entry;
struct map_source *map;
struct mapent_cache *mc;
struct mapent *me = NULL;
map = entry->maps;
while (map) {
/*
* Only consider map sources that have been read since
* the map entry was last updated.
*/
if (ap->entry->age > map->age) {
map = map->next;
continue;
}
mc = map->mc;
cache_readlock(mc);
if (type == LKP_DISTINCT)
me = cache_lookup_distinct(mc, key);
else
me = cache_lookup(mc, key);
if (me)
break;
cache_unlock(mc);
map = map->next;
}
return me;
}
/* Return with cache readlock held */
struct mapent *lookup_source_mapent(struct autofs_point *ap, const char *key, unsigned int type)
{
struct master_mapent *entry = ap->entry;
struct map_source *map;
struct mapent_cache *mc;
struct mapent *me = NULL;
map = entry->maps;
while (map) {
mc = map->mc;
cache_readlock(mc);
if (type == LKP_DISTINCT)
me = cache_lookup_distinct(mc, key);
else
me = cache_lookup(mc, key);
if (me)
break;
cache_unlock(mc);
map = map->next;
}
if (me && me->mc != mc)
error(LOGOPT_ANY, "mismatching mc in cache", me->key);
return me;
}
int lookup_source_close_ioctlfd(struct autofs_point *ap, const char *key)
{
struct master_mapent *entry = ap->entry;
struct map_source *map;
struct mapent_cache *mc;
struct mapent *me;
int ret = 0;
map = entry->maps;
while (map) {
mc = map->mc;
cache_readlock(mc);
me = cache_lookup_distinct(mc, key);
if (me) {
if (me->ioctlfd != -1) {
struct ioctl_ops *ops = get_ioctl_ops();
ops->close(ap->logopt, me->ioctlfd);
me->ioctlfd = -1;
}
cache_unlock(mc);
ret = 1;
break;
}
cache_unlock(mc);
map = map->next;
}
return ret;
}