/* ----------------------------------------------------------------------- * * * lookup_file.c - module for Linux automount to query a flat file map * * Copyright 1997 Transmeta Corporation - All Rights Reserved * Copyright 2001-2003 Ian Kent * * 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; incorporated herein by reference. * * ----------------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include #include #define MODULE_LOOKUP #include "automount.h" #include "nsswitch.h" #define MAPFMT_DEFAULT "sun" #define MODPREFIX "lookup(file): " #define MAX_INCLUDE_DEPTH 16 typedef enum { st_begin, st_compare, st_star, st_badent, st_entspc, st_getent } LOOKUP_STATE; typedef enum { got_nothing, got_star, got_real, got_plus } FOUND_STATE; typedef enum { esc_none, esc_char, esc_val, esc_all } ESCAPES; struct lookup_context { const char *mapname; int opts_argc; const char **opts_argv; time_t last_read; struct parse_mod *parse; }; int lookup_version = AUTOFS_LOOKUP_VERSION; /* Required by protocol */ static int do_init(const char *mapfmt, int argc, const char *const *argv, struct lookup_context *ctxt, unsigned int reinit) { int ret = 0; if (argc < 1) { logerr(MODPREFIX "No map name"); return 1; } ctxt->mapname = argv[0]; if (ctxt->mapname[0] != '/') { logmsg(MODPREFIX "file map %s is not an absolute pathname", argv[0]); return 1; } if (access(ctxt->mapname, R_OK)) { warn(LOGOPT_NONE, MODPREFIX "file map %s missing or not readable", argv[0]); return 1; } if (!mapfmt) mapfmt = MAPFMT_DEFAULT; argc--; argv++; ctxt->opts_argv = copy_argv(argc, (const char **) argv); if (ctxt->opts_argv == NULL) { warn(LOGOPT_NONE, MODPREFIX "failed to duplicate options"); return 1; } ctxt->opts_argc = argc; if (reinit) { ret = reinit_parse(ctxt->parse, mapfmt, MODPREFIX, argc, argv); if (ret) logmsg(MODPREFIX "failed to reinit parse context"); } else { ctxt->parse = open_parse(mapfmt, MODPREFIX, argc, argv); if (!ctxt->parse) { logmsg(MODPREFIX "failed to open parse context"); ret = 1; } } if (ret) free_argv(ctxt->opts_argc, ctxt->opts_argv); return ret; } int lookup_init(const char *mapfmt, int argc, const char *const *argv, void **context) { struct lookup_context *ctxt; char buf[MAX_ERR_BUF]; *context = NULL; ctxt = malloc(sizeof(struct lookup_context)); if (!ctxt) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); logerr(MODPREFIX "malloc: %s", estr); return 1; } memset(ctxt, 0, sizeof(struct lookup_context)); if (do_init(mapfmt, argc, argv, ctxt, 0)) { free(ctxt); return 1; } *context = ctxt; return 0; } int lookup_reinit(const char *mapfmt, int argc, const char *const *argv, void **context) { struct lookup_context *ctxt = (struct lookup_context *) *context; struct lookup_context *new; char buf[MAX_ERR_BUF]; int ret; new = malloc(sizeof(struct lookup_context)); if (!new) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); logerr(MODPREFIX "malloc: %s", estr); return 1; } memset(new, 0, sizeof(struct lookup_context)); new->parse = ctxt->parse; ret = do_init(mapfmt, argc, argv, new, 1); if (ret) { free(new); return 1; } *context = new; free_argv(ctxt->opts_argc, ctxt->opts_argv); free(ctxt); return 0; } static int read_one(unsigned logopt, FILE *f, char *key, unsigned int *k_len, char *mapent, unsigned int *m_len) { char *kptr, *p; int mapent_len, key_len; int ch, nch; LOOKUP_STATE state; FOUND_STATE getting, gotten; ESCAPES escape; kptr = key; p = NULL; mapent_len = key_len = 0; state = st_begin; memset(key, 0, KEY_MAX_LEN + 1); memset(mapent, 0, MAPENT_MAX_LEN + 1); getting = gotten = got_nothing; escape = esc_none; /* This is ugly. We can't just remove \ escape sequences in the value portion of an entry, because the parsing routine may need it. */ while ((ch = getc(f)) != EOF) { switch (escape) { case esc_none: if (ch == '\\') { /* Handle continuation lines */ if ((nch = getc(f)) == '\n') continue; ungetc(nch, f); escape = esc_char; } if (ch == '"') escape = esc_all; break; case esc_char: escape = esc_val; break; case esc_val: escape = esc_none; break; case esc_all: if (ch == '"') escape = esc_none; break; } switch (state) { case st_begin: if (!escape) { if (isspace(ch)); else if (ch == '#') state = st_badent; else if (ch == '*') { state = st_star; *(kptr++) = ch; key_len++; } else { if (ch == '+') gotten = got_plus; state = st_compare; *(kptr++) = ch; key_len++; } } else if (escape == esc_all) { state = st_compare; *(kptr++) = ch; key_len++; } else if (escape == esc_char); else state = st_badent; break; case st_compare: if (ch == '\n') { state = st_begin; if (gotten == got_plus) goto got_it; else if (escape == esc_all) { warn(logopt, MODPREFIX "unmatched \" in map key %s", key); goto next; } else if (escape != esc_val) goto got_it; } else if (isspace(ch) && !escape) { getting = got_real; state = st_entspc; if (gotten == got_plus) goto got_it; } else if (escape == esc_char); else { if (key_len == KEY_MAX_LEN) { state = st_badent; gotten = got_nothing; warn(logopt, MODPREFIX "map key \"%s...\" " "is too long. The maximum key " "length is %d", key, KEY_MAX_LEN); } else { if (escape == esc_val) { *(kptr++) = '\\'; key_len++; } *(kptr++) = ch; key_len++; } } break; case st_star: if (ch == '\n') state = st_begin; else if (isspace(ch) && gotten < got_star && !escape) { getting = got_star; state = st_entspc; } else if (escape != esc_char) state = st_badent; break; case st_badent: if (ch == '\n') { nch = getc(f); if (nch != EOF && isblank(nch)) { ungetc(nch, f); break; } ungetc(nch, f); state = st_begin; if (gotten == got_real || gotten == getting) goto got_it; warn(logopt, MODPREFIX "bad map entry \"%s...\" for key " "\"%s\"", mapent, key); goto next; } else if (!isblank(ch)) gotten = got_nothing; break; case st_entspc: if (ch == '\n') state = st_begin; else if (!isspace(ch) || escape) { if (escape) { if (escape == esc_char) break; if (ch <= 32) { getting = got_nothing; state = st_badent; break; } p = mapent; if (escape == esc_val) { *(p++) = '\\'; mapent_len++; } *(p++) = ch; mapent_len++; } else { p = mapent; *(p++) = ch; mapent_len = 1; } state = st_getent; gotten = getting; } break; case st_getent: if (ch == '\n') { if (escape == esc_all) { state = st_begin; warn(logopt, MODPREFIX "unmatched \" in %s for key %s", mapent, key); goto next; } nch = getc(f); if (nch != EOF && isblank(nch)) { ungetc(nch, f); state = st_badent; break; } ungetc(nch, f); state = st_begin; if (gotten == got_real || gotten == getting) goto got_it; } else if (mapent_len < MAPENT_MAX_LEN) { if (p) { mapent_len++; *(p++) = ch; } nch = getc(f); if (nch == EOF && (gotten == got_real || gotten == getting)) goto got_it; ungetc(nch, f); } else { warn(logopt, MODPREFIX "map entry \"%s...\" for key " "\"%s\" is too long. The maximum entry" " size is %d", mapent, key, MAPENT_MAX_LEN); state = st_badent; } break; } continue; got_it: if (gotten == got_nothing) goto next; *k_len = key_len; *m_len = mapent_len; return 1; next: kptr = key; p = NULL; mapent_len = key_len = 0; memset(key, 0, KEY_MAX_LEN + 1); memset(mapent, 0, MAPENT_MAX_LEN + 1); getting = gotten = got_nothing; escape = esc_none; } return 0; } static int check_master_self_include(struct master *master, struct lookup_context *ctxt) { char *m_path, *m_base, *i_path, *i_base; /* * If we are including a file map then check the * full path of the map. */ if (*master->name == '/') { if (!strcmp(master->name, ctxt->mapname)) return 1; else return 0; } /* Otherwise only check the map name itself. */ i_path = strdup(ctxt->mapname); if (!i_path) return 0; i_base = basename(i_path); m_path = strdup(master->name); if (!m_path) { free(i_path); return 0; } m_base = basename(m_path); if (!strcmp(m_base, i_base)) { free(i_path); free(m_path); return 1; } free(i_path); free(m_path); return 0; } int lookup_read_master(struct master *master, time_t age, void *context) { struct lookup_context *ctxt = (struct lookup_context *) context; unsigned int timeout = master->default_timeout; unsigned int logging = master->default_logging; unsigned int logopt = master->logopt; char *buffer; int blen; char path[KEY_MAX_LEN + 1]; char ent[MAPENT_MAX_LEN + 1]; FILE *f; unsigned int path_len, ent_len; int entry, cur_state; /* Don't return fail on self include, skip source */ if (master->recurse) return NSS_STATUS_TRYAGAIN; if (master->depth > MAX_INCLUDE_DEPTH) { error(logopt, MODPREFIX "maximum include depth exceeded %s", master->name); return NSS_STATUS_UNAVAIL; } f = open_fopen_r(ctxt->mapname); if (!f) { if (errno == ENOENT) return NSS_STATUS_NOTFOUND; error(logopt, MODPREFIX "could not open master map file %s", ctxt->mapname); return NSS_STATUS_UNAVAIL; } while(1) { entry = read_one(logopt, f, path, &path_len, ent, &ent_len); if (!entry) { if (feof(f)) break; if (ferror(f)) { warn(logopt, MODPREFIX "error reading map %s", ctxt->mapname); break; } continue; } debug(logopt, MODPREFIX "read entry %s", path); /* * If key starts with '+' it has to be an * included map. */ if (*path == '+') { char *save_name; unsigned int inc; int status; save_name = master->name; master->name = path + 1; inc = check_master_self_include(master, ctxt); if (inc) master->recurse = 1; master->depth++; status = lookup_nss_read_master(master, age); if (status != NSS_STATUS_SUCCESS) { warn(logopt, MODPREFIX "failed to read included master map %s", master->name); if (status != NSS_STATUS_NOTFOUND) { /* * If we're starting up we need the whole * master map initially, so tell the upper * layer to retry. */ master->read_fail = 1; } } master->depth--; master->recurse = 0; master->name = save_name; } else { blen = path_len + 1 + ent_len + 2; buffer = malloc(blen); if (!buffer) { error(logopt, MODPREFIX "could not malloc parse buffer"); fclose(f); return NSS_STATUS_UNAVAIL; } memset(buffer, 0, blen); strcpy(buffer, path); strcat(buffer, " "); strcat(buffer, ent); pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); master_parse_entry(buffer, timeout, logging, age); free(buffer); pthread_setcancelstate(cur_state, NULL); } if (feof(f)) break; } fclose(f); return NSS_STATUS_SUCCESS; } static int check_self_include(const char *key, struct lookup_context *ctxt) { char *m_key, *m_base, *i_key, *i_base; /* * If we are including a file map then check the * full path of the map. */ if (*(key + 1) == '/') { if (!strcmp(key + 1, ctxt->mapname)) return 1; else return 0; } i_key = strdup(key + 1); if (!i_key) return 0; i_base = basename(i_key); m_key = strdup(ctxt->mapname); if (!m_key) { free(i_key); return 0; } m_base = basename(m_key); if (!strcmp(m_base, i_base)) { free(i_key); free(m_key); return 1; } free(i_key); free(m_key); return 0; } static struct map_source * prepare_plus_include(struct autofs_point *ap, struct map_source *source, time_t age, char *key, unsigned int inc, struct lookup_context *ctxt) { struct map_source *new; struct map_type_info *info; const char *argv[2]; char **tmp_argv, **tmp_opts; int argc; char *buf; /* * TODO: * Initially just consider the passed in key to be a simple map * name (and possible source) and use the global map options in * the given autofs_point. ie. global options override. * * Later we might want to parse this and fill in the autofs_point * options fields. */ /* skip plus */ buf = strdup(key + 1); if (!buf) { error(ap->logopt, MODPREFIX "failed to strdup key"); return NULL; } if (!(info = parse_map_type_info(buf))) { error(ap->logopt, MODPREFIX "failed to parse map info"); free(buf); return NULL; } argc = 1; argv[0] = info->map; argv[1] = NULL; tmp_argv = (char **) copy_argv(argc, argv); if (!tmp_argv) { error(ap->logopt, MODPREFIX "failed to allocate args vector"); free_map_type_info(info); free(buf); return NULL; } tmp_opts = (char **) copy_argv(ctxt->opts_argc, ctxt->opts_argv); if (!tmp_opts) { error(ap->logopt, MODPREFIX "failed to allocate options args vector"); free_argv(argc, (const char **) tmp_argv); free_map_type_info(info); free(buf); return NULL; } tmp_argv = append_argv(argc, tmp_argv, ctxt->opts_argc, tmp_opts); if (!tmp_argv) { error(ap->logopt, MODPREFIX "failed to append options vector"); free_map_type_info(info); free(buf); return NULL; } argc += ctxt->opts_argc; new = master_find_source_instance(source, info->type, info->format, argc, (const char **) tmp_argv); if (new) { /* * Make sure included map age is in sync with its owner * or we could incorrectly wipe out its entries. */ new->age = age; new->stale = 1; } else { new = master_add_source_instance(source, info->type, info->format, age, argc, (const char **) tmp_argv); if (!new) { free_argv(argc, (const char **) tmp_argv); free_map_type_info(info); free(buf); error(ap->logopt, "failed to add included map instance"); return NULL; } } free_argv(argc, (const char **) tmp_argv); new->depth = source->depth + 1; if (inc) new->recurse = 1; free_map_type_info(info); free(buf); return new; } int lookup_read_map(struct autofs_point *ap, time_t age, void *context) { struct lookup_context *ctxt = (struct lookup_context *) context; struct map_source *source; struct mapent_cache *mc; char key[KEY_MAX_LEN + 1]; char mapent[MAPENT_MAX_LEN + 1]; FILE *f; unsigned int k_len, m_len; int entry; source = ap->entry->current; ap->entry->current = NULL; master_source_current_signal(ap->entry); mc = source->mc; if (source->recurse) return NSS_STATUS_TRYAGAIN; if (source->depth > MAX_INCLUDE_DEPTH) { error(ap->logopt, "maximum include depth exceeded %s", ctxt->mapname); return NSS_STATUS_UNAVAIL; } f = open_fopen_r(ctxt->mapname); if (!f) { if (errno == ENOENT) return NSS_STATUS_NOTFOUND; error(ap->logopt, MODPREFIX "could not open map file %s", ctxt->mapname); return NSS_STATUS_UNAVAIL; } while(1) { entry = read_one(ap->logopt, f, key, &k_len, mapent, &m_len); if (!entry) { if (feof(f)) break; if (ferror(f)) { warn(ap->logopt, MODPREFIX "error reading map %s", ctxt->mapname); break; } continue; } /* * If key starts with '+' it has to be an * included map. */ if (*key == '+') { struct map_source *inc_source; unsigned int inc; int status; debug(ap->logopt, "read included map %s", key); inc = check_self_include(key, ctxt); inc_source = prepare_plus_include(ap, source, age, key, inc, ctxt); if (!inc_source) { debug(ap->logopt, "failed to select included map %s", key); continue; } /* Gim'ee some o' that 16k stack baby !! */ status = lookup_nss_read_map(ap, inc_source, age); if (!status) { warn(ap->logopt, "failed to read included map %s", key); } } else { char *s_key; if (source->flags & MAP_FLAG_FORMAT_AMD) { if (!strcmp(key, "/defaults")) { cache_writelock(mc); cache_update(mc, source, key, mapent, age); cache_unlock(mc); continue; } /* Don't fail on "/" in key => type == 0 */ s_key = sanitize_path(key, k_len, 0, ap->logopt); if (!s_key) continue; } else { s_key = sanitize_path(key, k_len, ap->type, ap->logopt); if (!s_key) continue; } cache_writelock(mc); cache_update(mc, source, s_key, mapent, age); cache_unlock(mc); free(s_key); } if (feof(f)) break; } source->age = age; ctxt->last_read = time(NULL); fclose(f); return NSS_STATUS_SUCCESS; } static int match_key(struct autofs_point *ap, struct map_source *source, char *map_key, const char *key, size_t key_len, const char *mapent) { char buf[MAX_ERR_BUF]; struct mapent_cache *mc; time_t age = monotonic_time(NULL); char *lkp_key; char *prefix; size_t map_key_len; int ret, eq; mc = source->mc; /* exact match is a match for both autofs and amd */ eq = strcmp(map_key, key); if (eq == 0) { cache_writelock(mc); ret = cache_update(mc, source, key, mapent, age); cache_unlock(mc); return ret; } if (!(source->flags & MAP_FLAG_FORMAT_AMD)) return CHE_FAIL; map_key_len = strlen(map_key); lkp_key = strdup(key); if (!lkp_key) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); error(ap->logopt, MODPREFIX "strdup: %s", estr); return CHE_FAIL; } ret = CHE_FAIL; if (map_key_len > (strlen(lkp_key) + 2)) goto done; /* * Now strip successive directory components and try a * match against map entries ending with a wildcard and * finally try the wilcard entry itself. If we get a match * then update the cache with the read key and its mapent. */ while ((prefix = strrchr(lkp_key, '/'))) { size_t len; *prefix = '\0'; len = strlen(lkp_key); eq = strncmp(map_key, lkp_key, len); if (!eq && map_key[len + 1] == '*') { cache_writelock(mc); ret = cache_update(mc, source, map_key, mapent, age); cache_unlock(mc); goto done; } } done: free(lkp_key); return ret; } static int lookup_one(struct autofs_point *ap, struct map_source *source, const char *key, int key_len, struct lookup_context *ctxt) { struct mapent_cache *mc = source->mc; char mkey[KEY_MAX_LEN + 1]; char mapent[MAPENT_MAX_LEN + 1]; time_t age = monotonic_time(NULL); FILE *f; unsigned int k_len, m_len; int entry, ret; f = open_fopen_r(ctxt->mapname); if (!f) { error(ap->logopt, MODPREFIX "could not open map file %s", ctxt->mapname); return CHE_FAIL; } while(1) { entry = read_one(ap->logopt, f, mkey, &k_len, mapent, &m_len); if (entry) { /* * If key starts with '+' it has to be an * included map. */ if (*mkey == '+') { struct map_source *inc_source; unsigned int inc; int status; debug(ap->logopt, MODPREFIX "lookup included map %s", mkey); inc = check_self_include(mkey, ctxt); inc_source = prepare_plus_include(ap, source, age, mkey, inc, ctxt); if (!inc_source) { debug(ap->logopt, MODPREFIX "failed to select included map %s", key); continue; } /* Gim'ee some o' that 16k stack baby !! */ status = lookup_nss_mount(ap, inc_source, key, key_len); if (status) { fclose(f); return CHE_COMPLETED; } } else { char *s_key; if (source->flags & MAP_FLAG_FORMAT_AMD) { if (!strcmp(mkey, "/defaults")) { cache_writelock(mc); cache_update(mc, source, mkey, mapent, age); cache_unlock(mc); continue; } /* Don't fail on "/" in key => type == 0 */ s_key = sanitize_path(mkey, k_len, 0, ap->logopt); if (!s_key) continue; } else { s_key = sanitize_path(mkey, k_len, ap->type, ap->logopt); if (!s_key) continue; if (key_len != strlen(s_key)) { free(s_key); continue; } } ret = match_key(ap, source, s_key, key, key_len, mapent); if (ret == CHE_FAIL) { free(s_key); continue; } free(s_key); fclose(f); return ret; } } if (feof(f)) break; if (ferror(f)) { warn(ap->logopt, MODPREFIX "error reading map %s", ctxt->mapname); break; } } fclose(f); return CHE_MISSING; } static int lookup_wild(struct autofs_point *ap, struct map_source *source, struct lookup_context *ctxt) { struct mapent_cache *mc; char mkey[KEY_MAX_LEN + 1]; char mapent[MAPENT_MAX_LEN + 1]; time_t age = monotonic_time(NULL); FILE *f; unsigned int k_len, m_len; int entry, ret; mc = source->mc; f = open_fopen_r(ctxt->mapname); if (!f) { error(ap->logopt, MODPREFIX "could not open map file %s", ctxt->mapname); return CHE_FAIL; } while(1) { entry = read_one(ap->logopt, f, mkey, &k_len, mapent, &m_len); if (entry) { int eq; eq = (*mkey == '*' && k_len == 1); if (eq == 0) continue; cache_writelock(mc); ret = cache_update(mc, source, "*", mapent, age); cache_unlock(mc); fclose(f); return ret; } if (feof(f)) break; if (ferror(f)) { warn(ap->logopt, MODPREFIX "error reading map %s", ctxt->mapname); break; } } fclose(f); return CHE_MISSING; } static int check_map_indirect(struct autofs_point *ap, struct map_source *source, char *key, int key_len, struct lookup_context *ctxt) { struct mapent_cache *mc; struct mapent *exists; int ret = CHE_OK; mc = source->mc; ret = lookup_one(ap, source, key, key_len, ctxt); if (ret == CHE_COMPLETED) return NSS_STATUS_COMPLETED; if (ret == CHE_FAIL) return NSS_STATUS_NOTFOUND; cache_writelock(mc); if (source->flags & MAP_FLAG_FORMAT_AMD) exists = match_cached_key(ap, MODPREFIX, source, key); else exists = cache_lookup_distinct(mc, key); /* Not found in the map but found in the cache */ if (exists && exists->source == source && ret & CHE_MISSING) { if (exists->mapent) { free(exists->mapent); exists->mapent = NULL; exists->status = 0; } } cache_unlock(mc); if (ret == CHE_MISSING) { struct mapent *we; int wild = CHE_MISSING; wild = lookup_wild(ap, source, ctxt); /* * Check for map change and update as needed for * following cache lookup. */ cache_writelock(mc); we = cache_lookup_distinct(mc, "*"); if (we) { /* Wildcard entry existed and is now gone */ if (we->source == source && (wild & CHE_MISSING)) cache_delete(mc, "*"); } cache_unlock(mc); if (wild & (CHE_OK | CHE_UPDATED)) return NSS_STATUS_SUCCESS; } if (ret == CHE_MISSING) return NSS_STATUS_NOTFOUND; return NSS_STATUS_SUCCESS; } static int map_update_needed(struct autofs_point *ap, struct map_source *source, struct lookup_context *ctxt) { struct stat st; int ret = 1; /* * We can skip the map lookup and cache update altogether * if we know the map hasn't been modified since it was * last read. If it has then we can mark the map stale * so a re-read is triggered following the lookup. */ if (stat(ctxt->mapname, &st)) { error(ap->logopt, MODPREFIX "file map %s, could not stat", ctxt->mapname); return -1; } if (st.st_mtime <= ctxt->last_read) { /* * If any map instances are present for this source * then either we have plus included entries or we * are looking through the list of nsswitch sources. * In either case, or if it's a "multi" source, we * cannot avoid reading through the map because we * must preserve the key order over multiple sources * or maps. But also, we can't know, at this point, * if a source instance has been changed since the * last time we checked it. */ if (!source->instance && source->type && strcmp(source->type, "multi")) ret = 0; } else source->stale = 1; return ret; } int lookup_mount(struct autofs_point *ap, const char *name, int name_len, void *context) { struct lookup_context *ctxt = (struct lookup_context *) context; struct map_source *source; struct mapent_cache *mc; struct mapent *me; char key[KEY_MAX_LEN + 1]; int key_len; char *lkp_key; char *mapent = NULL; char mapent_buf[MAPENT_MAX_LEN + 1]; char buf[MAX_ERR_BUF]; int status = 0; int ret = 1; source = ap->entry->current; ap->entry->current = NULL; master_source_current_signal(ap->entry); mc = source->mc; if (source->recurse) return NSS_STATUS_UNAVAIL; if (source->depth > MAX_INCLUDE_DEPTH) { error(ap->logopt, MODPREFIX "maximum include depth exceeded %s", ctxt->mapname); return NSS_STATUS_SUCCESS; } debug(ap->logopt, MODPREFIX "looking up %s", name); if (!(source->flags & MAP_FLAG_FORMAT_AMD)) { key_len = snprintf(key, KEY_MAX_LEN + 1, "%s", name); if (key_len > KEY_MAX_LEN) return NSS_STATUS_NOTFOUND; } else { key_len = expandamdent(name, NULL, NULL); if (key_len > KEY_MAX_LEN) return NSS_STATUS_NOTFOUND; memset(key, 0, KEY_MAX_LEN + 1); expandamdent(name, key, NULL); debug(ap->logopt, MODPREFIX "expanded key: \"%s\"", key); } /* Check if we recorded a mount fail for this key anywhere */ me = lookup_source_mapent(ap, key, LKP_DISTINCT); if (me) { if (me->status >= monotonic_time(NULL)) { cache_unlock(me->mc); return NSS_STATUS_NOTFOUND; } else { struct mapent_cache *smc = me->mc; struct mapent *sme; if (me->mapent) cache_unlock(smc); else { cache_unlock(smc); cache_writelock(smc); sme = cache_lookup_distinct(smc, key); /* Negative timeout expired for non-existent entry. */ if (sme && !sme->mapent) { if (cache_pop_mapent(sme) == CHE_FAIL) cache_delete(smc, key); } cache_unlock(smc); } } } /* * We can't check the direct mount map as if it's not in * the map cache already we never get a mount lookup, so * we never know about it. */ if (ap->type == LKP_INDIRECT && *key != '/') { int ret; ret = map_update_needed(ap, source, ctxt); if (!ret) goto do_cache_lookup; /* Map isn't accessable, just try the cache */ if (ret < 0) goto do_cache_lookup; cache_readlock(mc); me = cache_lookup_distinct(mc, key); if (me && me->multi) lkp_key = strdup(me->multi->key); else if (!ap->pref) lkp_key = strdup(key); else { lkp_key = malloc(strlen(ap->pref) + strlen(key) + 1); if (lkp_key) { strcpy(lkp_key, ap->pref); strcat(lkp_key, key); } } cache_unlock(mc); if (!lkp_key) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); error(ap->logopt, MODPREFIX "malloc: %s", estr); return NSS_STATUS_UNKNOWN; } status = check_map_indirect(ap, source, lkp_key, strlen(lkp_key), ctxt); free(lkp_key); if (status) { if (status == NSS_STATUS_COMPLETED) return NSS_STATUS_SUCCESS; return NSS_STATUS_NOTFOUND; } } /* * We can't take the writelock for direct mounts. If we're * starting up or trying to re-connect to an existing direct * mount we could be iterating through the map entries with * the readlock held. But we don't need to update the cache * when we're starting up so just take the readlock in that * case. */ do_cache_lookup: if (ap->flags & MOUNT_FLAG_REMOUNT) cache_readlock(mc); else cache_writelock(mc); if (!ap->pref) lkp_key = strdup(key); else { lkp_key = malloc(strlen(ap->pref) + strlen(key) + 1); if (lkp_key) { strcpy(lkp_key, ap->pref); strcat(lkp_key, key); } } if (!lkp_key) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); error(ap->logopt, MODPREFIX "malloc: %s", estr); cache_unlock(mc); return NSS_STATUS_UNKNOWN; } me = match_cached_key(ap, MODPREFIX, source, lkp_key); if (me && me->mapent) { if (me && (me->source == source || *me->key == '/')) { strcpy(mapent_buf, me->mapent); mapent = mapent_buf; } } cache_unlock(mc); if (!me) { free(lkp_key); return NSS_STATUS_NOTFOUND; } if (!mapent) { free(lkp_key); return NSS_STATUS_TRYAGAIN; } debug(ap->logopt, MODPREFIX "%s -> %s", lkp_key, mapent); free(lkp_key); master_source_current_wait(ap->entry); ap->entry->current = source; ret = ctxt->parse->parse_mount(ap, key, key_len, mapent, ctxt->parse->context); if (ret) { /* Don't update negative cache when re-connecting */ if (ap->flags & MOUNT_FLAG_REMOUNT) return NSS_STATUS_TRYAGAIN; cache_writelock(mc); cache_update_negative(mc, source, key, ap->negative_timeout); cache_unlock(mc); return NSS_STATUS_TRYAGAIN; } return NSS_STATUS_SUCCESS; } int lookup_done(void *context) { struct lookup_context *ctxt = (struct lookup_context *) context; int rv = close_parse(ctxt->parse); free_argv(ctxt->opts_argc, ctxt->opts_argv); free(ctxt); return rv; }