/* ----------------------------------------------------------------------- * * * lookup_hosts.c - module for Linux automount to mount the exports * from a given host * * Copyright 2005 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 /* * Avoid annoying compiler noise by using an alternate name for * typedef name in mount.h */ #define name __dummy_type_name #include "mount.h" #undef name #define MODULE_LOOKUP #include "automount.h" #include "nsswitch.h" #define MAPFMT_DEFAULT "sun" #define MODPREFIX "lookup(hosts): " pthread_mutex_t hostent_mutex = PTHREAD_MUTEX_INITIALIZER; struct lookup_context { struct parse_mod *parse; }; int lookup_version = AUTOFS_LOOKUP_VERSION; /* Required by protocol */ exports rpc_get_exports(const char *host, long seconds, long micros, unsigned int option); void rpc_exports_free(exports list); 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; } mapfmt = MAPFMT_DEFAULT; ctxt->parse = open_parse(mapfmt, MODPREFIX, argc, argv); if (!ctxt->parse) { logerr(MODPREFIX "failed to open parse context"); 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; int ret; mapfmt = MAPFMT_DEFAULT; ret = reinit_parse(ctxt->parse, mapfmt, MODPREFIX, argc, argv); if (ret) return 1; return 0; } int lookup_read_master(struct master *master, time_t age, void *context) { return NSS_STATUS_UNKNOWN; } static char *get_exports(struct autofs_point *ap, const char *host) { char buf[MAX_ERR_BUF]; char *mapent; exports exp, this; debug(ap->logopt, MODPREFIX "fetchng export list for %s", host); exp = rpc_get_exports(host, 10, 0, RPC_CLOSE_NOLINGER); mapent = NULL; this = exp; while (this) { if (mapent) { int len = strlen(mapent) + 1; len += strlen(host) + 2*(strlen(this->ex_dir) + 2) + 3; mapent = realloc(mapent, len); if (!mapent) { char *estr; estr = strerror_r(errno, buf, MAX_ERR_BUF); error(ap->logopt, MODPREFIX "malloc: %s", estr); rpc_exports_free(exp); return NULL; } strcat(mapent, " \""); strcat(mapent, this->ex_dir); strcat(mapent, "\""); } else { int len = 2*(strlen(this->ex_dir) + 2) + strlen(host) + 3; mapent = malloc(len); if (!mapent) { char *estr; estr = strerror_r(errno, buf, MAX_ERR_BUF); error(ap->logopt, MODPREFIX "malloc: %s", estr); rpc_exports_free(exp); return NULL; } strcpy(mapent, "\""); strcat(mapent, this->ex_dir); strcat(mapent, "\""); } strcat(mapent, " \""); strcat(mapent, host); strcat(mapent, ":"); strcat(mapent, this->ex_dir); strcat(mapent, "\""); this = this->ex_next; } rpc_exports_free(exp); if (!mapent) error(ap->logopt, MODPREFIX "exports lookup failed for %s", host); return mapent; } static int do_parse_mount(struct autofs_point *ap, struct map_source *source, const char *name, int name_len, char *mapent, struct lookup_context *ctxt) { int ret; master_source_current_wait(ap->entry); ap->entry->current = source; ret = ctxt->parse->parse_mount(ap, name, name_len, mapent, ctxt->parse->context); if (ret) { struct mapent_cache *mc = source->mc; /* 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, name, ap->negative_timeout); cache_unlock(mc); return NSS_STATUS_TRYAGAIN; } return NSS_STATUS_SUCCESS; } static void update_hosts_mounts(struct autofs_point *ap, struct map_source *source, time_t age, struct lookup_context *ctxt) { struct mapent_cache *mc; struct mapent *me; char *mapent; int ret; mc = source->mc; pthread_cleanup_push(cache_lock_cleanup, mc); cache_writelock(mc); me = cache_lookup_first(mc); while (me) { /* Hosts map entry not yet expanded or already expired */ if (!me->multi) goto next; debug(ap->logopt, MODPREFIX "get list of exports for %s", me->key); mapent = get_exports(ap, me->key); if (mapent) { cache_update(mc, source, me->key, mapent, age); free(mapent); } next: me = cache_lookup_next(mc, me); } pthread_cleanup_pop(1); pthread_cleanup_push(cache_lock_cleanup, mc); cache_readlock(mc); me = cache_lookup_first(mc); while (me) { /* * Hosts map entry not yet expanded, already expired * or not the base of the tree */ if (!me->multi || me->multi != me) goto cont; debug(ap->logopt, MODPREFIX "attempt to update exports for %s", me->key); master_source_current_wait(ap->entry); ap->entry->current = source; ap->flags |= MOUNT_FLAG_REMOUNT; ret = ctxt->parse->parse_mount(ap, me->key, strlen(me->key), me->mapent, ctxt->parse->context); if (ret) warn(ap->logopt, MODPREFIX "failed to parse mount %s", me->mapent); ap->flags &= ~MOUNT_FLAG_REMOUNT; cont: me = cache_lookup_next(mc, me); } pthread_cleanup_pop(1); } 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; struct hostent *host; int status; source = ap->entry->current; ap->entry->current = NULL; master_source_current_signal(ap->entry); mc = source->mc; debug(ap->logopt, MODPREFIX "read hosts map"); /* * If we don't need to create directories then there's no use * reading the map. We always need to read the whole map for * direct mounts in order to mount the triggers. */ if (!(ap->flags & MOUNT_FLAG_GHOST) && ap->type != LKP_DIRECT) { debug(ap->logopt, MODPREFIX "map not browsable, update existing host entries only"); update_hosts_mounts(ap, source, age, ctxt); source->age = age; return NSS_STATUS_SUCCESS; } status = pthread_mutex_lock(&hostent_mutex); if (status) { error(ap->logopt, MODPREFIX "failed to lock hostent mutex"); return NSS_STATUS_UNAVAIL; } sethostent(0); while ((host = gethostent()) != NULL) { pthread_cleanup_push(cache_lock_cleanup, mc); cache_writelock(mc); cache_update(mc, source, host->h_name, NULL, age); cache_unlock(mc); pthread_cleanup_pop(0); } endhostent(); status = pthread_mutex_unlock(&hostent_mutex); if (status) error(ap->logopt, MODPREFIX "failed to unlock hostent mutex"); update_hosts_mounts(ap, source, age, ctxt); source->age = age; return NSS_STATUS_SUCCESS; } 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 *mapent = NULL; int mapent_len; time_t now = monotonic_time(NULL); int ret; source = ap->entry->current; ap->entry->current = NULL; master_source_current_signal(ap->entry); mc = source->mc; /* Check if we recorded a mount fail for this key anywhere */ me = lookup_source_mapent(ap, name, 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, name); /* Negative timeout expired for non-existent entry. */ if (sme && !sme->mapent) { if (cache_pop_mapent(sme) == CHE_FAIL) cache_delete(smc, name); } cache_unlock(smc); } } } cache_readlock(mc); me = cache_lookup_distinct(mc, name); if (!me) { cache_unlock(mc); /* * We haven't read the list of hosts into the * cache so go straight to the lookup. */ if (!(ap->flags & MOUNT_FLAG_GHOST)) { /* * If name contains a '/' we're searching for an * offset that doesn't exist in the export list * so it's NOTFOUND otherwise this could be a * lookup for a new host. */ if (*name != '/' && strchr(name, '/')) return NSS_STATUS_NOTFOUND; goto done; } if (*name == '/') info(ap->logopt, MODPREFIX "can't find path in hosts map %s", name); else info(ap->logopt, MODPREFIX "can't find path in hosts map %s/%s", ap->path, name); debug(ap->logopt, MODPREFIX "lookup failed - update exports list"); goto done; } /* * Host map export entries are added to the cache as * direct mounts. If the name we seek starts with a slash * it must be a mount request for one of the exports. */ if (*name == '/') { pthread_cleanup_push(cache_lock_cleanup, mc); mapent_len = strlen(me->mapent); mapent = malloc(mapent_len + 1); if (mapent) strcpy(mapent, me->mapent); pthread_cleanup_pop(0); } cache_unlock(mc); done: debug(ap->logopt, MODPREFIX "%s -> %s", name, mapent); if (!mapent) { /* We need to get the exports list and update the cache. */ mapent = get_exports(ap, name); /* Exports lookup failed so we're outa here */ if (!mapent) return NSS_STATUS_UNAVAIL; cache_writelock(mc); cache_update(mc, source, name, mapent, now); cache_unlock(mc); } ret = do_parse_mount(ap, source, name, name_len, mapent, ctxt); free(mapent); return ret; } int lookup_done(void *context) { struct lookup_context *ctxt = (struct lookup_context *) context; int rv = close_parse(ctxt->parse); free(ctxt); return rv; }