/*
* Copyright (c) 1997-2014 Erez Zadok
* Copyright (c) 1989 Jan-Simon Pendry
* Copyright (c) 1989 Imperial College of Science, Technology & Medicine
* Copyright (c) 1989 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.
*
*
* File: am-utils/amd/mapc.c
*
*/
/*
* Map type
*/
typedef struct map_type map_type;
struct map_type {
char *name; /* Name of this map type */
init_fn *init; /* Initialization */
reload_fn *reload; /* Reload or fill */
isup_fn *isup; /* Is service up or not? (1=up, 0=down) */
search_fn *search; /* Search for new entry */
mtime_fn *mtime; /* Find modify time */
int def_alloc; /* Default allocation mode */
};
/*
* Map for root node
*/
static mnt_map *root_map;
/*
* List of known maps
*/
qelem map_list_head = {&map_list_head, &map_list_head};
i = sizeof(maptypes) / sizeof(maptypes[0]);
lastmt = maptypes + i;
buf[0] = '\0';
for (mt = maptypes; mt < lastmt; mt++) {
xstrlcat(buf, mt->name, l);
if (mt == (lastmt-1))
break; /* if last one, don't do xstrlcat's that follows */
linesize += strlen(mt->name);
if (--i > 0) {
xstrlcat(buf, ", ", l);
linesize += 2;
}
if (linesize > 54) {
linesize = 0;
xstrlcat(buf, "\n\t\t ", l);
}
}
}
/*
* Check if a map of a certain type exists.
* Return 1 (true) if exists, 0 (false) if not.
*/
int
mapc_type_exists(const char *type)
{
map_type *mt;
if (!type)
return 0;
for (mt = maptypes;
mt < maptypes + sizeof(maptypes) / sizeof(maptypes[0]);
mt++) {
if (STREQ(type, mt->name))
return 1;
}
return 0; /* not found anywhere */
}
/*
* 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 HAVE_REGEXEC
regex_t re;
#endif /* HAVE_REGEXEC */
dlog("add_kv: %s -> %s", key, val);
if (val != NULL && strchr(val, '\n') != NULL) {
/*
* If the entry value contains multiple lines we need to break
* them up and add them recursively. This is a workaround to
* support Sun style multi-mounts. Amd converts Sun style
* mulit-mounts to type:=auto. The problem is that Sun packs all
* the entries on one line. When Amd does the conversion it puts
* each type:=auto entry on the same line separated by '\n'.
*/
char *entry, *tok;
/*
* The first line should contain the first entry. The key for
* this entry is the key passed into this function.
*/
if ((tok = strtok(val, "\n")) != NULL) {
mapc_add_kv(m, key, xstrdup(tok));
}
/*
* For the rest of the entries we need to tokenize them by '\n'
* and separate the keys from there entries.
*/
while ((tok = strtok(NULL, "\n")) != NULL) {
key = tok;
/* find the entry */
for (entry = key; *entry && !isspace((unsigned char)*entry); entry++);
if (*entry) {
*entry++ = '\0';
}
mapc_add_kv(m, xstrdup(key), xstrdup(entry));
}
XFREE(val);
return;
}
#ifdef HAVE_REGEXEC
if (MAPC_ISRE(m)) {
char pattern[MAXPATHLEN];
int retval;
/*
* Make sure the string is bound to the start and end
*/
xsnprintf(pattern, sizeof(pattern), "^%s$", key);
retval = regcomp(&re, pattern, REG_ICASE);
if (retval != 0) {
char errstr[256];
/* XXX: this code was recently ported, and must be tested -Erez */
errstr[0] = '\0';
regerror(retval, &re, errstr, 256);
plog(XLOG_USER, "error compiling RE \"%s\": %s", pattern, errstr);
return;
}
} else
memset(&re, 0, sizeof(re));
#endif /* HAVE_REGEXEC */
/*
* 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 = NULL;
}
/*
* Do a map reload.
* Attempt to reload without losing current data by switching the hashes
* round.
* If reloading was needed and succeeded, return 1; else return 0.
*/
static int
mapc_reload_map(mnt_map *m)
{
int error, ret = 0;
kv *maphash[NKVHASH];
time_t t;
error = (*m->mtime) (m, m->map_name, &t);
if (error) {
t = m->modify;
}
/*
* skip reloading maps that have not been modified, unless
* amq -f was used (do_mapc_reload is 0)
*/
if (m->reloads != 0 && do_mapc_reload != 0) {
if (t <= m->modify) {
plog(XLOG_INFO, "reload of map %s is not needed (in sync)", m->map_name);
dlog("map %s last load time is %d, last modify time is %d",
m->map_name, (int) m->modify, (int) t);
return ret;
}
}
/* copy the old hash and zero the map */
memcpy((voidp) maphash, (voidp) m->kvhash, sizeof(m->kvhash));
memset((voidp) m->kvhash, 0, sizeof(m->kvhash));
dlog("calling map reload on %s", m->map_name);
m->nentries = 0;
error = (*m->reload) (m, m->map_name, mapc_add_kv);
if (error) {
if (m->reloads == 0)
plog(XLOG_FATAL, "first time load of map %s failed!", m->map_name);
else
plog(XLOG_ERROR, "reload of map %s failed - using old values",
m->map_name);
mapc_clear(m);
memcpy((voidp) m->kvhash, (voidp) maphash, sizeof(m->kvhash));
} else {
if (m->reloads++ == 0)
plog(XLOG_INFO, "first time load of map %s succeeded", m->map_name);
else
plog(XLOG_INFO, "reload #%d of map %s succeeded",
m->reloads, m->map_name);
mapc_clear_kvhash(maphash);
if (m->wildcard) {
XFREE(m->wildcard);
m->wildcard = NULL;
}
m->modify = t;
ret = 1;
}
dlog("calling mapc_search for wildcard");
error = mapc_search(m, wildcard, &m->wildcard);
if (error)
m->wildcard = NULL;
return ret;
}
/*
* If using a configuration file, and the map_type is defined, then look
* for it, in the maptypes array. If found, initialize the map using that
* map_type. If not found, return error. If no map_type was defined,
* default to cycling through all maptypes.
*/
if (use_conf_file && type) {
/* find what type of map this one is */
for (mt = maptypes;
mt < maptypes + sizeof(maptypes) / sizeof(maptypes[0]);
mt++) {
if (STREQ(type, mt->name)) {
plog(XLOG_INFO, "initializing amd.conf map %s of type %s", map, type);
if ((*mt->init) (m, map, &modify) == 0) {
break;
} else {
plog(XLOG_ERROR, "failed to initialize map %s", map);
error_init(m, map, &modify);
break;
}
}
} /* end of "for (mt =" loop */
} else { /* cycle through all known maptypes */
/*
* not using amd conf file or using it by w/o specifying map type
*/
for (mt = maptypes;
mt < maptypes + sizeof(maptypes) / sizeof(maptypes[0]);
mt++) {
dlog("trying to initialize map %s of type %s ...", map, mt->name);
if ((*mt->init) (m, map, &modify) == 0) {
break;
}
}
} /* end of "if (use_conf_file && (colpos = strchr ..." statement */
switch (alloc) {
default:
plog(XLOG_USER, "Ambiguous map cache type \"%s\"; using \"inc\"", opt);
alloc = MAPC_INC;
/* fall-through... */
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 HAVE_REGEXEC
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 /* HAVE_REGEXEC */
}
dlog("Map for %s coming from maptype %s", map, mt->name);
/*
* synchronize cache with reality
*/
mapc_sync(m);
return m;
}
/*
* Free the cached data in a map hash
*/
static void
mapc_clear_kvhash(kv **kvhash)
{
int i;
/*
* For each of the hash slots, chain
* along free'ing the data.
*/
for (i = 0; i < NKVHASH; i++) {
kv *k = kvhash[i];
while (k) {
kv *n = k->next;
XFREE(k->key);
XFREE(k->val);
XFREE(k);
k = n;
}
}
}
/*
* Free the cached data in a map
*/
static void
mapc_clear(mnt_map *m)
{
mapc_clear_kvhash(m->kvhash);
/*
* Zero the hash slots
*/
memset((voidp) m->kvhash, 0, sizeof(m->kvhash));
/*
* Free the wildcard if it exists
*/
XFREE(m->wildcard);
m->wildcard = NULL;
m->nentries = 0;
}
/*
* Find a map, or create one if it does not exist
*/
mnt_map *
mapc_find(char *map, char *opt, const char *maptype, const char *mntpt)
{
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, maptype, mntpt);
ins_que(&m->hdr, &map_list_head);
/*
* Decrement the reference count.
* If the reference count hits zero
* then throw the map away.
*/
if (m && --m->refc == 0) {
mapc_clear(m);
XFREE(m->map_name);
rem_que(&m->hdr);
XFREE(m);
}
}
/*
* Search the map for the key. Put a safe (malloc'ed) copy in *pval or
* return an error code
*/
static int
mapc_meta_search(mnt_map *m, char *key, char **pval, int recurse)
{
int error = 0;
kv *k = NULL;
/*
* 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, m->map_name, &t);
if (error || t > m->modify) {
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 HAVE_REGEXEC
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) {
int retval;
/* XXX: this code was recently ported, and must be tested -Erez */
retval = regexec(&k->re, key, 0, NULL, 0);
if (retval == 0) { /* succeeded */
break;
} else { /* failed to match, log error */
char errstr[256];
errstr[0] = '\0';
regerror(retval, &k->re, errstr, 256);
plog(XLOG_USER, "error matching RE \"%s\" against \"%s\": %s",
key, k->key, errstr);
}
k = k->next;
}
if (k)
break;
}
}
#endif /* HAVE_REGEXEC */
/*
* If found then take a copy
*/
if (k) {
if (k->val)
*pval = xstrdup(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, xstrdup(key), xstrdup(*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 / *"
*/
xstrlcpy(wildname, key, sizeof(wildname));
while (error && (subp = strrchr(wildname, '/'))) {
/*
* sizeof space left in subp is sizeof wildname minus what's left
* after the strchr above returned a pointer inside wildname into
* subp.
*/
xstrlcpy(subp, "/*", sizeof(wildname) - (subp - wildname));
dlog("mapc recurses on %s", wildname);
error = mapc_meta_search(m, wildname, pval, MREC_PART);
if (error)
*subp = '\0';
}
/*
* Get map cache in sync with physical representation
*/
static void
mapc_sync(mnt_map *m)
{
int need_mtime_update = 0;
if (m->alloc == MAPC_ROOT)
return; /* nothing to do */
/* do not clear map if map service is down */
if (m->isup) {
if (!((*m->isup)(m, m->map_name))) {
plog(XLOG_ERROR, "mapc_sync: map %s is down: not clearing map", m->map_name);
return;
}
}
if (m->alloc >= MAPC_ALL) {
/* mapc_reload_map() always works */
need_mtime_update = mapc_reload_map(m);
} else {
mapc_clear(m);
/*
* Attempt to find the wildcard entry
*/
mapc_find_wildcard(m);
need_mtime_update = 1; /* because mapc_clear always works */
}
/*
* To be safe, update the mtime of the mnt_map's own node, so that the
* kernel will flush all of its cached entries.
*/
if (need_mtime_update && m->cfm) {
am_node *mp = find_ap(m->cfm->cfm_dir);
if (mp) {
clocktime(&mp->am_fattr.na_mtime);
} else {
plog(XLOG_ERROR, "cannot find map %s to update its mtime",
m->cfm->cfm_dir);
}
}
}
/*
* 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(mnt_map *m, char *map, time_t *tp)
{
*tp = clocktime(NULL);
return STREQ(map, ROOT_MAP) ? 0 : ENOENT;
}
/*
* Add a new entry to the root map
*
* dir - directory (key)
* opts - mount options
* map - map name
* cfm - optional amd configuration file map section structure
*/
void
root_newmap(const char *dir, const char *opts, const char *map, const cf_map_t *cfm)
{
char str[MAXPATHLEN];
/*
* First make sure we have a root map to talk about...
*/
if (!root_map)
root_map = mapc_find(ROOT_MAP, "mapdefault", NULL, NULL);
/*
* Then add the entry...
*/
/*
* Here I plug in the code to process other amd.conf options like
* map_type, search_path, and flags (browsable_dirs, mount_type).
*/
int
mapc_keyiter(mnt_map *m, key_fun *fn, opaque_t 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 on the root map and call (*fn)() on the key of all the nodes.
* Returns the number of entries in the root map.
*/
int
root_keyiter(key_fun *fn, opaque_t arg)
{
if (root_map) {
int c = mapc_keyiter(root_map, fn, arg);
return c;
}
return 0;
}
/*
* Error map
*/
static int
error_init(mnt_map *m, char *map, time_t *tp)
{
plog(XLOG_USER, "No source data for map %s", map);
*tp = 0;