| /* ----------------------------------------------------------------------- * |
| * |
| * lookup_hosts.c - module for Linux automount to mount the exports |
| * from a given host |
| * |
| * Copyright 2005 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; incorporated herein by reference. |
| * |
| * ----------------------------------------------------------------------- */ |
| |
| #include <stdio.h> |
| #include <malloc.h> |
| #include <sys/param.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <netdb.h> |
| |
| /* |
| * 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; |
| } |