/* $OpenBSD: mapc.c,v 1.12 2003/10/30 16:04:06 millert Exp $ */ /*- * Copyright (c) 1989 Jan-Simon Pendry * Copyright (c) 1989 Imperial College of Science, Technology & Medicine * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Jan-Simon Pendry at Imperial College, London. * * 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. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. */ #ifndef lint /*static char sccsid[] = "from: @(#)mapc.c 8.1 (Berkeley) 6/6/93";*/ static char *rcsid = "$Id: mapc.c,v 1.12 2003/10/30 16:04:06 millert Exp $"; #endif /* not lint */ /* * Mount map cache */ #include "am.h" #ifdef HAS_REGEXP #include RE_HDR #endif /* * Hash table size */ #define NKVHASH (1 << 2) /* Power of two */ /* * Wildcard key */ static char wildcard[] = "*"; /* * Map cache types * default, none, incremental, all, regexp * MAPC_RE implies MAPC_ALL and must be numerically * greater. */ #define MAPC_DFLT 0x000 #define MAPC_NONE 0x001 #define MAPC_INC 0x002 #define MAPC_ROOT 0x004 #define MAPC_ALL 0x010 #ifdef HAS_REGEXP #define MAPC_RE 0x020 #define MAPC_ISRE(m) ((m)->alloc == MAPC_RE) #else #define MAPC_ISRE(m) FALSE #endif #define MAPC_CACHE_MASK 0x0ff #define MAPC_SYNC 0x100 static struct opt_tab mapc_opt[] = { { "all", MAPC_ALL }, { "default", MAPC_DFLT }, { "inc", MAPC_INC }, { "mapdefault", MAPC_DFLT }, { "none", MAPC_NONE }, #ifdef HAS_REGEXP { "re", MAPC_RE }, { "regexp", MAPC_RE }, #endif { "sync", MAPC_SYNC }, { 0, 0 } }; /* * Lookup recursion */ #define MREC_FULL 2 #define MREC_PART 1 #define MREC_NONE 0 /* * Cache map operations */ typedef void add_fn(mnt_map *, char *, char *); typedef int init_fn(char *, time_t *); typedef int search_fn(mnt_map *, char *, char *, char **, time_t *); typedef int reload_fn(mnt_map *, char *, add_fn *); typedef int mtime_fn(char *, time_t *); static void mapc_sync(mnt_map *); /* * Map type */ typedef struct map_type map_type; struct map_type { char *name; /* Name of this map type */ init_fn *init; /* Initialisation */ reload_fn *reload; /* Reload or fill */ search_fn *search; /* Search for new entry */ mtime_fn *mtime; /* Find modify time */ int def_alloc; /* Default allocation mode */ }; /* * Key-value pair */ typedef struct kv kv; struct kv { kv *next; char *key; char *val; }; struct mnt_map { qelem hdr; int refc; /* Reference count */ short flags; /* Allocation flags */ short alloc; /* Allocation mode */ time_t modify; /* Modify time of map */ char *map_name; /* Name of this map */ char *wildcard; /* Wildcard value */ reload_fn *reload; /* Function to be used for reloads */ search_fn *search; /* Function to be used for searching */ mtime_fn *mtime; /* Modify time function */ kv *kvhash[NKVHASH]; /* Cached data */ }; /* * Map for root node */ static mnt_map *root_map; /* * List of known maps */ extern qelem map_list_head; qelem map_list_head = { &map_list_head, &map_list_head }; /* * Configuration */ /* ROOT MAP */ static int root_init(char *, time_t *); /* FILE MAPS */ #ifdef HAS_FILE_MAPS extern int file_init(char *, time_t *); extern int file_reload(mnt_map *, char *, add_fn *); extern int file_search(mnt_map *, char *, char *, char **, time_t *); extern int file_mtime(char *, time_t *); #endif /* HAS_FILE_MAPS */ /* Network Information Service (NIS) MAPS */ #ifdef HAS_NIS_MAPS extern int nis_init(char *, time_t *); #ifdef HAS_NIS_RELOAD extern int nis_reload(mnt_map *, char *, add_fn *); #else #define nis_reload error_reload #endif extern int nis_search(mnt_map *, char *, char *, char **, time_t *); #define nis_mtime nis_init #endif /* HAS_NIS_MAPS */ /* NDBM MAPS */ #ifdef HAS_NDBM_MAPS #ifdef OS_HAS_NDBM extern int ndbm_init(char *, time_t *); extern int ndbm_search(mnt_map *, char *, charo *, char **, time_t *); #define ndbm_mtime ndbm_init #endif /* OS_HAS_NDBM */ #endif /* HAS_NDBM_MAPS */ /* PASSWD MAPS */ #ifdef HAS_PASSWD_MAPS extern int passwd_init(char *, time_t *); extern int passwd_search(mnt_map *, char *, char *, char **, time_t *); #endif /* HAS_PASSWD_MAPS */ /* HESIOD MAPS */ #ifdef HAS_HESIOD_MAPS extern int hesiod_init(char *, time_t *); #ifdef HAS_HESIOD_RELOAD extern int hesiod_reload(mnt_map *, char *, add_fn *); #else #define hesiod_reload error_reload #endif extern int hesiod_search(mnt_map *, char *, char *, char **, time_t *); #endif /* HAS_HESIOD_MAPS */ /* UNION MAPS */ #ifdef HAS_UNION_MAPS extern int union_init(char *, time_t *); extern int union_search(mnt_map *, char *, char *, char **, time_t *); extern int union_reload(mnt_map *, char *, add_fn *); #endif /* HAS_UNION_MAPS */ /* ERROR MAP */ static int error_init(char *, time_t *); static int error_reload(mnt_map *, char *, add_fn *); static int error_search(mnt_map *, char *, char *, char **, time_t *); static int error_mtime(char *, time_t *); static map_type maptypes[] = { { "root", root_init, error_reload, error_search, error_mtime, MAPC_ROOT }, #ifdef HAS_PASSWD_MAPS { "passwd", passwd_init, error_reload, passwd_search, error_mtime, MAPC_INC }, #endif #ifdef HAS_HESIOD_MAPS { "hesiod", hesiod_init, hesiod_reload, hesiod_search, error_mtime, MAPC_ALL }, #endif #ifdef HAS_UNION_MAPS { "union", union_init, union_reload, union_search, error_mtime, MAPC_ALL }, #endif #ifdef HAS_NIS_MAPS { "nis", nis_init, nis_reload, nis_search, nis_mtime, MAPC_INC }, #endif #ifdef HAS_NDBM_MAPS { "ndbm", ndbm_init, error_reload, ndbm_search, ndbm_mtime, MAPC_INC }, #endif #ifdef HAS_FILE_MAPS { "file", file_init, file_reload, file_search, file_mtime, MAPC_ALL }, #endif { "error", error_init, error_reload, error_search, error_mtime, MAPC_NONE }, }; /* * Hash function */ static unsigned int kvhash_of(char *key) { unsigned int i, j; for (i = 0; (j = *key++); i += j) ; return i % NKVHASH; } void mapc_showtypes(FILE *fp) { map_type *mt; char *sep = ""; for (mt = maptypes; mt < maptypes+sizeof(maptypes)/sizeof(maptypes[0]); mt++) { fprintf(fp, "%s%s", sep, mt->name); sep = ", "; } } static const char *reg_error = "?"; void regerror(const char *m) { reg_error = m; } /* * Add key and val to the map m. * key and val are assumed to be safe copies */ void mapc_add_kv(mnt_map *m, char *key, char *val) { kv **h; kv *n; int hash = kvhash_of(key); #ifdef DEBUG dlog("add_kv: %s -> %s", key, val); #endif #ifdef HAS_REGEXP if (MAPC_ISRE(m)) { char keyb[MAXPATHLEN]; regexp *re; /* * Make sure the string is bound to the start and end */ snprintf(keyb, sizeof(keyb), "^%s$", key); re = regcomp(keyb); if (re == 0) { plog(XLOG_USER, "error compiling RE \"%s\": %s", keyb, reg_error); return; } else { free(key); key = (char *) re; } } #endif h = &m->kvhash[hash]; n = ALLOC(kv); n->key = key; n->val = val; n->next = *h; *h = n; } void mapc_repl_kv(mnt_map *m, char *key, char *val) { kv *k; /* * Compute the hash table offset */ k = m->kvhash[kvhash_of(key)]; /* * Scan the linked list for the key */ while (k && !FSTREQ(k->key, key)) k = k->next; if (k) { free(k->val); k->val = val; } else { mapc_add_kv(m, key, val); } } /* * Search a map for a key. * Calls map specific search routine. * While map is out of date, keep re-syncing. */ static int search_map(mnt_map *m, char *key, char **valp) { int rc; do { rc = (*m->search)(m, m->map_name, key, valp, &m->modify); if (rc < 0) { plog(XLOG_MAP, "Re-synchronizing cache for map %s", m->map_name); mapc_sync(m); } } while (rc < 0); return rc; } /* * Do a wildcard lookup in the map and * save the result. */ static void mapc_find_wildcard(mnt_map *m) { /* * Attempt to find the wildcard entry */ int rc = search_map(m, wildcard, &m->wildcard); if (rc != 0) m->wildcard = 0; } /* * Make a duplicate reference to an existing map */ #define mapc_dup(m) ((m)->refc++, (m)) /* * Do a map reload */ static int mapc_reload_map(mnt_map *m) { int error; #ifdef DEBUG dlog("calling map reload on %s", m->map_name); #endif error = (*m->reload)(m, m->map_name, mapc_add_kv); if (error) return error; m->wildcard = 0; #ifdef DEBUG dlog("calling mapc_search for wildcard"); #endif error = mapc_search(m, wildcard, &m->wildcard); if (error) m->wildcard = 0; return 0; } /* * Create a new map */ static mnt_map * mapc_create(char *map, char *opt) { mnt_map *m = ALLOC(mnt_map); map_type *mt; time_t modify; int alloc = 0; (void) cmdoption(opt, mapc_opt, &alloc); for (mt = maptypes; mt < maptypes+sizeof(maptypes)/sizeof(maptypes[0]); mt++) if ((*mt->init)(map, &modify) == 0) break; /* assert: mt in maptypes */ m->flags = alloc & ~MAPC_CACHE_MASK; alloc &= MAPC_CACHE_MASK; if (alloc == MAPC_DFLT) alloc = mt->def_alloc; switch (alloc) { default: plog(XLOG_USER, "Ambiguous map cache type \"%s\"; using \"inc\"", opt); alloc = MAPC_INC; /* fallthrough... */ case MAPC_NONE: case MAPC_INC: case MAPC_ROOT: break; case MAPC_ALL: /* * If there is no support for reload and it was requested * then back off to incremental instead. */ if (mt->reload == error_reload) { plog(XLOG_WARNING, "Map type \"%s\" does not support cache type \"all\"; using \"inc\"", mt->name); alloc = MAPC_INC; } break; #ifdef HAS_REGEXP case MAPC_RE: if (mt->reload == error_reload) { plog(XLOG_WARNING, "Map type \"%s\" does not support cache type \"re\"", mt->name); mt = &maptypes[sizeof(maptypes)/sizeof(maptypes[0]) - 1]; /* assert: mt->name == "error" */ } break; #endif } #ifdef DEBUG dlog("Map for %s coming from maptype %s", map, mt->name); #endif m->alloc = alloc; m->reload = mt->reload; m->modify = modify; m->search = alloc >= MAPC_ALL ? error_search : mt->search; m->mtime = mt->mtime; bzero((void *)m->kvhash, sizeof(m->kvhash)); m->map_name = strdup(map); m->refc = 1; m->wildcard = 0; /* * synchronize cache with reality */ mapc_sync(m); return m; } /* * Free the cached data in a map */ static void mapc_clear(mnt_map *m) { int i; /* * For each of the hash slots, chain * along free'ing the data. */ for (i = 0; i < NKVHASH; i++) { kv *k = m->kvhash[i]; while (k) { kv *n = k->next; free((void *)k->key); if (k->val) free((void *)k->val); free((void *)k); k = n; } } /* * Zero the hash slots */ bzero((void *)m->kvhash, sizeof(m->kvhash)); /* * Free the wildcard if it exists */ if (m->wildcard) { free(m->wildcard); m->wildcard = 0; } } /* * Find a map, or create one if it does not exist */ mnt_map * mapc_find(char *map, char *opt) { mnt_map *m; /* * Search the list of known maps to see if * it has already been loaded. If it is found * then return a duplicate reference to it. * Otherwise make a new map as required and * add it to the list of maps */ ITER(m, mnt_map, &map_list_head) if (STREQ(m->map_name, map)) return mapc_dup(m); m = mapc_create(map, opt); ins_que(&m->hdr, &map_list_head); return m; } /* * Free a map. */ void mapc_free(mnt_map *m) { /* * Decrement the reference count. * If the reference count hits zero * then throw the map away. */ if (m && --m->refc == 0) { mapc_clear(m); free((void *)m->map_name); rem_que(&m->hdr); free((void *)m); } } /* * Search the map for the key. * Put a safe copy in *pval or return * an error code */ int mapc_meta_search(mnt_map *m, char *key, char **pval, int recurse) { int error = 0; kv *k = 0; /* * Firewall */ if (!m) { plog(XLOG_ERROR, "Null map request for %s", key); return ENOENT; } if (m->flags & MAPC_SYNC) { /* * Get modify time... */ time_t t; error = (*m->mtime)(m->map_name, &t); if (error || t > m->modify) { m->modify = t; plog(XLOG_INFO, "Map %s is out of date", m->map_name); mapc_sync(m); } } if (!MAPC_ISRE(m)) { /* * Compute the hash table offset */ k = m->kvhash[kvhash_of(key)]; /* * Scan the linked list for the key */ while (k && !FSTREQ(k->key, key)) k = k->next; } #ifdef HAS_REGEXP else if (recurse == MREC_FULL) { /* * Try for an RE match against the entire map. * Note that this will be done in a "random" * order. */ int i; for (i = 0; i < NKVHASH; i++) { k = m->kvhash[i]; while (k) { if (regexec((regexp *) k->key, key)) break; k = k->next; } if (k) break; } } #endif /* * If found then take a copy */ if (k) { if (k->val) *pval = strdup(k->val); else error = ENOENT; } else if (m->alloc >= MAPC_ALL) { /* * If the entire map is cached then this * key does not exist. */ error = ENOENT; } else { /* * Otherwise search the map. If we are * in incremental mode then add the key * to the cache. */ error = search_map(m, key, pval); if (!error && m->alloc == MAPC_INC) mapc_add_kv(m, strdup(key), strdup(*pval)); } /* * If an error, and a wildcard exists, * and the key is not internal then * return a copy of the wildcard. */ if (error > 0) { if (recurse == MREC_FULL && !MAPC_ISRE(m)) { char wildname[MAXPATHLEN]; char *subp; if (*key == '/') return error; /* * Keep chopping sub-directories from the RHS * and replacing with "/ *" and repeat the lookup. * For example: * "src/gnu/gcc" -> "src / gnu / *" -> "src / *" */ strlcpy(wildname, key, sizeof wildname); while (error && (subp = strrchr(wildname, '/'))) { strlcpy(subp, "/*", 3); #ifdef DEBUG dlog("mapc recurses on %s", wildname); #endif error = mapc_meta_search(m, wildname, pval, MREC_PART); if (error) *subp = 0; } if (error > 0 && m->wildcard) { *pval = strdup(m->wildcard); error = 0; } } } return error; } int mapc_search(mnt_map *m, char *key, char **pval) { return mapc_meta_search(m, key, pval, MREC_FULL); } /* * Get map cache in sync with physical representation */ static void mapc_sync(mnt_map *m) { if (m->alloc != MAPC_ROOT) { mapc_clear(m); if (m->alloc >= MAPC_ALL) if (mapc_reload_map(m)) m->alloc = MAPC_INC; /* * Attempt to find the wildcard entry */ if (m->alloc < MAPC_ALL) mapc_find_wildcard(m); } } /* * Reload all the maps * Called when Amd gets hit by a SIGHUP. */ void mapc_reload(void) { mnt_map *m; /* * For all the maps, * Throw away the existing information. * Do a reload * Find the wildcard */ ITER(m, mnt_map, &map_list_head) mapc_sync(m); } /* * Root map. * The root map is used to bootstrap amd. * All the require top-level mounts are added * into the root map and then the map is iterated * and a lookup is done on all the mount points. * This causes the top level mounts to be automounted. */ static int root_init(char *map, time_t *tp) { *tp = clocktime(); return strcmp(map, ROOT_MAP) == 0 ? 0 : ENOENT; } /* * Add a new entry to the root map * * dir - directory (key) * opts - mount options * map - map name */ void root_newmap(char *dir, char *opts, char *map) { char str[MAXPATHLEN]; /* * First make sure we have a root map to talk about... */ if (!root_map) root_map = mapc_find(ROOT_MAP, "mapdefault"); /* * Then add the entry... */ dir = strdup(dir); if (map) snprintf(str, sizeof(str), "cache:=mapdefault;type:=toplvl;fs:=\"%s\";%s", map, opts ? opts : ""); else strlcpy(str, opts, sizeof str); mapc_repl_kv(root_map, dir, strdup(str)); } int mapc_keyiter(mnt_map *m, void (*fn)(char *,void *), void *arg) { int i; int c = 0; for (i = 0; i < NKVHASH; i++) { kv *k = m->kvhash[i]; while (k) { (*fn)(k->key, arg); k = k->next; c++; } } return c; } /* * Iterate over the root map * and call (*fn)() on the key * of all the nodes. * Finally throw away the root map. */ int root_keyiter(void (*fn)(char *,void *), void *arg) { if (root_map) { int c = mapc_keyiter(root_map, fn, arg); #ifdef notdef mapc_free(root_map); root_map = 0; #endif return c; } return 0; } /* * Error map */ static int error_init(char *map, time_t *tp) { plog(XLOG_USER, "No source data for map %s", map); *tp = 0; return 0; } /*ARGSUSED*/ static int error_search(mnt_map *m, char *map, char *key, char **pval, time_t *tp) { return ENOENT; } /*ARGSUSED*/ static int error_reload(mnt_map *m, char *map, add_fn *fn) { return ENOENT; } static int error_mtime(char *map, time_t *tp) { *tp = 0; return 0; }