/* ----------------------------------------------------------------------- * * * parse_sun.c - module for Linux automountd to parse a Sun-format * automounter map * * Copyright 1997 Transmeta Corporation - All Rights Reserved * Copyright 2000 Jeremy Fitzhardinge * Copyright 2004, 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 #include #include #include #include #include #include #include #include #include #include #define MODULE_PARSE #include "automount.h" #define MODPREFIX "parse(sun): " int parse_version = AUTOFS_PARSE_VERSION; /* Required by protocol */ static struct mount_mod *mount_nfs = NULL; static int init_ctr = 0; static pthread_mutex_t instance_mutex = PTHREAD_MUTEX_INITIALIZER; static void instance_mutex_lock(void) { int status = pthread_mutex_lock(&instance_mutex); if (status) fatal(status); } static void instance_mutex_unlock(void) { int status = pthread_mutex_unlock(&instance_mutex); if (status) fatal(status); } extern const char *global_options; struct parse_context { char *optstr; /* Mount options */ char *macros; /* Map wide macro defines */ struct substvar *subst; /* $-substitutions */ int slashify_colons; /* Change colons to slashes? */ }; struct multi_mnt { char *path; char *options; char *location; struct multi_mnt *next; }; /* Default context */ static struct parse_context default_context = { NULL, /* No mount options */ NULL, /* No map wide macros */ NULL, /* The substvar local vars table */ 1 /* Do slashify_colons */ }; int destroy_logpri_fifo(struct autofs_point *ap); static char *concat_options(char *left, char *right); /* Free all storage associated with this context */ static void kill_context(struct parse_context *ctxt) { macro_lock(); macro_free_table(ctxt->subst); macro_unlock(); if (ctxt->optstr) free(ctxt->optstr); if (ctxt->macros) free(ctxt->macros); free(ctxt); } /* * $- and &-expand a Sun-style map entry and return the length of the entry. * If "dst" is NULL, just count the length. */ int expandsunent(const char *src, char *dst, const char *key, const struct substvar *svc, int slashify_colons) { const struct substvar *sv; int len, l, seen_colons; const char *p; char ch; len = 0; seen_colons = 0; while ((ch = *src++)) { switch (ch) { case '&': l = strlen(key); /* * In order to ensure that any isspace() characters * in the key are preserved, we need to escape them * here. */ const char *keyp = key; while (*keyp) { if (isspace(*keyp)) { if (dst) { *dst++ = '\\'; *dst++ = *keyp++; } else keyp++; l++; } else { if (dst) *dst++ = *keyp++; else keyp++; } } len += l; break; case '$': if (*src == '{') { p = strchr(++src, '}'); if (!p) { /* Ignore rest of string */ if (dst) *dst = '\0'; return len; } sv = macro_findvar(svc, src, p - src); if (sv) { l = strlen(sv->val); if (dst) { strcpy(dst, sv->val); dst += l; } len += l; } src = p + 1; } else { p = src; while (isalnum(*p) || *p == '_') p++; sv = macro_findvar(svc, src, p - src); if (sv) { l = strlen(sv->val); if (dst) { strcpy(dst, sv->val); dst += l; } len += l; } src = p; } break; case '\\': len++; if (dst) *dst++ = ch; if (*src) { len++; if (dst) *dst++ = *src; src++; } break; case '"': len++; if (dst) *dst++ = ch; while (*src && *src != '"') { len++; if (dst) *dst++ = *src; src++; } if (*src && dst) { len++; *dst++ = *src++; } break; case ':': if (dst) *(dst++) = (seen_colons && slashify_colons) ? '/' : ':'; len++; /* Were looking for the colon preceeding a path */ if (*src == '/') seen_colons = 1; break; default: if (isspace(ch)) seen_colons = 0; if (dst) *(dst++) = ch; len++; break; } } if (dst) *dst = '\0'; return len; } static int do_init(int argc, const char *const *argv, struct parse_context *ctxt) { char *noptstr, *def, *val, *macros, *gbl_options; char buf[MAX_ERR_BUF]; int optlen, len, offset; const char *xopt; int i, bval; unsigned int append_options; optlen = 0; /* Look for options and capture, and create new defines if we need to */ for (i = 0; i < argc; i++) { if (argv[i][0] == '-' && (argv[i][1] == 'D' || argv[i][1] == '-') ) { switch (argv[i][1]) { case 'D': if (argv[i][2]) def = strdup(argv[i] + 2); else if (++i < argc) def = strdup(argv[i]); else break; if (!def) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); logerr(MODPREFIX "strdup: %s", estr); break; } val = strchr(def, '='); if (val) *(val++) = '\0'; else val = ""; macro_lock(); ctxt->subst = macro_addvar(ctxt->subst, def, strlen(def), val); macro_unlock(); /* we use 5 for the "-D", "=", "," and the null */ if (ctxt->macros) { len = strlen(ctxt->macros) + strlen(def) + strlen(val); macros = realloc(ctxt->macros, len + 5); if (!macros) { free(def); break; } strcat(macros, ","); } else { /* No comma, so only +4 */ len = strlen(def) + strlen(val); macros = malloc(len + 4); if (!macros) { free(def); break; } *macros = '\0'; } ctxt->macros = macros; strcat(ctxt->macros, "-D"); strcat(ctxt->macros, def); strcat(ctxt->macros, "="); strcat(ctxt->macros, val); free(def); break; case '-': if (!strncmp(argv[i] + 2, "no-", 3)) { xopt = argv[i] + 5; bval = 0; } else { xopt = argv[i] + 2; bval = 1; } if (!strmcmp(xopt, "slashify-colons", 1)) ctxt->slashify_colons = bval; else error(LOGOPT_ANY, MODPREFIX "unknown option: %s", argv[i]); break; default: error(LOGOPT_ANY, MODPREFIX "unknown option: %s", argv[i]); break; } } else { offset = (argv[i][0] == '-' ? 1 : 0); len = strlen(argv[i] + offset); if (ctxt->optstr) { noptstr = (char *) realloc(ctxt->optstr, optlen + len + 2); if (noptstr) { noptstr[optlen] = ','; strcpy(noptstr + optlen + 1, argv[i] + offset); optlen += len + 1; } } else { noptstr = (char *) malloc(len + 1); if (noptstr) { strcpy(noptstr, argv[i] + offset); optlen = len; } } if (!noptstr) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); logerr(MODPREFIX "%s", estr); return 1; } ctxt->optstr = noptstr; } } gbl_options = NULL; if (global_options) { if (ctxt->optstr && strstr(ctxt->optstr, global_options)) goto options_done; gbl_options = strdup(global_options); } if (gbl_options) { append_options = defaults_get_append_options(); if (append_options) { char *tmp = concat_options(gbl_options, ctxt->optstr); if (!tmp) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); logerr(MODPREFIX "concat_options: %s", estr); free(gbl_options); } else ctxt->optstr = tmp; } else { if (!ctxt->optstr) ctxt->optstr = gbl_options; else free(gbl_options); } } options_done: debug(LOGOPT_NONE, MODPREFIX "init gathered global options: %s", ctxt->optstr); return 0; } int parse_init(int argc, const char *const *argv, void **context) { struct parse_context *ctxt; char buf[MAX_ERR_BUF]; *context = NULL; /* Set up context and escape chain */ ctxt = (struct parse_context *) malloc(sizeof(struct parse_context)); if (!ctxt) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); logerr(MODPREFIX "malloc: %s", estr); return 1; } *ctxt = default_context; if (do_init(argc, argv, ctxt)) { free(ctxt); return 1; } /* We only need this once. NFS mounts are so common that we cache this module. */ instance_mutex_lock(); if (mount_nfs) init_ctr++; else { if ((mount_nfs = open_mount("nfs", MODPREFIX))) { init_ctr++; } else { kill_context(ctxt); instance_mutex_unlock(); return 1; } } instance_mutex_unlock(); *context = (void *) ctxt; return 0; } int parse_reinit(int argc, const char *const *argv, void **context) { struct parse_context *ctxt = (struct parse_context *) *context; struct parse_context *new; char buf[MAX_ERR_BUF]; new = (struct parse_context *) malloc(sizeof(struct parse_context)); if (!new) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); logerr(MODPREFIX "malloc: %s", estr); return 1; } *new = default_context; if (do_init(argc, argv, new)) { free(new); return 1; } kill_context(ctxt); *context = (void *) new; return 0; } static const char *parse_options(const char *str, char **ret, unsigned int logopt) { const char *cp = str; int len; if (*cp++ != '-') return str; if (*ret != NULL) free(*ret); len = chunklen(cp, 0); *ret = dequote(cp, len, logopt); return cp + len; } static char *concat_options(char *left, char *right) { char buf[MAX_ERR_BUF]; char *ret; if (left == NULL || *left == '\0') { ret = strdup(right); free(right); return ret; } if (right == NULL || *right == '\0') { ret = strdup(left); free(left); return ret; } ret = malloc(strlen(left) + strlen(right) + 2); if (ret == NULL) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); logerr(MODPREFIX "malloc: %s", estr); return NULL; } strcpy(ret, left); strcat(ret, ","); strcat(ret, right); free(left); free(right); return ret; } static int sun_mount(struct autofs_point *ap, const char *root, const char *name, int namelen, const char *loc, int loclen, const char *options, struct parse_context *ctxt) { char *fstype = "nfs"; /* Default filesystem type */ int nonstrict = 1; int use_weight_only = ap->flags & MOUNT_FLAG_USE_WEIGHT_ONLY; int rv, cur_state; char *mountpoint; char *what; char *type; if (*options == '\0') options = NULL; if (options) { char *noptions; const char *comma; char *np; int len = strlen(options) + 1; noptions = np = alloca(len); *np = '\0'; /* Extract fstype= pseudo option */ for (comma = options; *comma != '\0';) { const char *cp; while (*comma == ',') comma++; cp = comma; while (*comma != '\0' && *comma != ',') comma++; if (_strncmp("fstype=", cp, 7) == 0) { int typelen = comma - (cp + 7); fstype = alloca(typelen + 1); memcpy(fstype, cp + 7, typelen); fstype[typelen] = '\0'; } else if (_strncmp("nonstrict", cp, 9) == 0) { nonstrict = 1; } else if (_strncmp("strict", cp, 6) == 0 && comma - cp == 6) { nonstrict = 0; } else if (_strncmp("nobrowse", cp, 8) == 0 || _strncmp("browse", cp, 6) == 0 || _strncmp("timeout=", cp, 8) == 0) { if (strcmp(fstype, "autofs") == 0 || strstr(cp, "fstype=autofs")) { memcpy(np, cp, comma - cp + 1); np += comma - cp + 1; } } else if (_strncmp("no-use-weight-only", cp, 18) == 0) { use_weight_only = -1; } else if (_strncmp("use-weight-only", cp, 15) == 0) { use_weight_only = MOUNT_FLAG_USE_WEIGHT_ONLY; } else if (_strncmp("bg", cp, 2) == 0 || _strncmp("nofg", cp, 4) == 0) { continue; } else { memcpy(np, cp, comma - cp + 1); np += comma - cp + 1; } } if (np > noptions + len) { warn(ap->logopt, MODPREFIX "options string truncated"); np[len] = '\0'; } else *(np - 1) = '\0'; options = noptions; } if (!strcmp(fstype, "autofs") && ctxt->macros) { char *noptions = NULL; if (!options || *options == '\0') { noptions = alloca(strlen(ctxt->macros) + 1); *noptions = '\0'; } else { int len = strlen(options) + strlen(ctxt->macros) + 2; noptions = alloca(len); if (noptions) { strcpy(noptions, options); strcat(noptions, ","); } } if (noptions && *noptions != '\0') { strcat(noptions, ctxt->macros); options = noptions; } else { error(ap->logopt, MODPREFIX "alloca failed for options"); } } mountpoint = alloca(namelen + 1); sprintf(mountpoint, "%.*s", namelen, name); type = ap->entry->maps->type; if (type && !strcmp(type, "hosts")) { if (options && *options != '\0') { int len = strlen(options); int suid = strstr(options, "suid") ? 0 : 7; int dev = strstr(options, "dev") ? 0 : 6; int nointr = strstr(options, "nointr") ? 0 : 5; if (suid || dev || nointr) { char *tmp = alloca(len + suid + dev + nointr + 1); if (!tmp) { error(ap->logopt, MODPREFIX "alloca failed for options"); if (nonstrict) return -1; return 1; } strcpy(tmp, options); if (suid) strcat(tmp, ",nosuid"); if (dev) strcat(tmp, ",nodev"); if (nointr) strcat(tmp, ",intr"); options = tmp; } } else { char *tmp = alloca(18); if (!tmp) { error(ap->logopt, MODPREFIX "alloca failed for options"); if (nonstrict) return -1; return 1; } strcpy(tmp, "nosuid,nodev,intr"); options = tmp; } } pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); if (!strcmp(fstype, "nfs") || !strcmp(fstype, "nfs4")) { what = alloca(loclen + 1); memcpy(what, loc, loclen); what[loclen] = '\0'; /* Add back "[no-]use-weight-only" for NFS mounts only */ if (use_weight_only) { char *tmp; int len; if (options && *options != '\0') { len = strlen(options) + 19; tmp = alloca(len); strcpy(tmp, options); strcat(tmp, ","); if (use_weight_only == MOUNT_FLAG_USE_WEIGHT_ONLY) strcat(tmp, "use-weight-only"); else strcat(tmp, "no-use-weight-only"); } else { tmp = alloca(19); if (use_weight_only == MOUNT_FLAG_USE_WEIGHT_ONLY) strcpy(tmp, "use-weight-only"); else strcpy(tmp, "no-use-weight-only"); } options = tmp; } debug(ap->logopt, MODPREFIX "mounting root %s, mountpoint %s, " "what %s, fstype %s, options %s", root, mountpoint, what, fstype, options); rv = mount_nfs->mount_mount(ap, root, mountpoint, strlen(mountpoint), what, fstype, options, mount_nfs->context); } else { if (!loclen) what = NULL; else { what = alloca(loclen + 1); if (*loc == ':') { loclen--; memcpy(what, loc + 1, loclen); what[loclen] = '\0'; } else { memcpy(what, loc, loclen); what[loclen] = '\0'; } } debug(ap->logopt, MODPREFIX "mounting root %s, mountpoint %s, " "what %s, fstype %s, options %s", root, mountpoint, what, fstype, options); /* Generic mount routine */ rv = do_mount(ap, root, mountpoint, strlen(mountpoint), what, fstype, options); } pthread_setcancelstate(cur_state, NULL); if (nonstrict && rv) return -rv; return rv; } /* * Scan map entry looking for evidence it has multiple key/mapent * pairs. */ static int check_is_multi(const char *mapent) { const char *p = mapent; int multi = 0; int not_first_chunk = 0; if (!p) { logerr(MODPREFIX "unexpected NULL map entry pointer"); return 0; } if (*p == '"') p++; /* If first character is "/" it's a multi-mount */ if (*p == '/') return 1; while (*p) { p = skipspace(p); /* * After the first chunk there can be additional * locations (possibly not multi) or possibly an * options string if the first entry includes the * optional '/' (is multi). Following this any * path that begins with '/' indicates a mutil-mount * entry. */ if (not_first_chunk) { if (*p == '"') p++; if (*p == '/' || *p == '-') { multi = 1; break; } } while (*p == '-') { p += chunklen(p, 0); p = skipspace(p); } /* * Expect either a path or location * after which it's a multi mount. */ p += chunklen(p, check_colon(p)); not_first_chunk++; } return multi; } static int update_offset_entry(struct autofs_point *ap, const char *name, const char *m_root, int m_root_len, const char *path, const char *myoptions, const char *loc, time_t age) { struct map_source *source; struct mapent_cache *mc; char m_key[PATH_MAX + 1]; char m_mapent[MAPENT_MAX_LEN + 1]; int p_len, m_key_len, m_options_len, m_mapent_len; int ret; source = ap->entry->current; ap->entry->current = NULL; master_source_current_signal(ap->entry); mc = source->mc; memset(m_mapent, 0, MAPENT_MAX_LEN + 1); /* Internal hosts map may have loc == NULL */ if (!*path) { error(ap->logopt, MODPREFIX "syntax error in offset %s -> %s", path, loc); return CHE_FAIL; } p_len = strlen(path); /* Trailing '/' causes us pain */ if (p_len > 1) { while (p_len > 1 && path[p_len - 1] == '/') p_len--; } m_key_len = m_root_len + p_len; if (m_key_len > PATH_MAX) { error(ap->logopt, MODPREFIX "multi mount key too long"); return CHE_FAIL; } strcpy(m_key, m_root); strncat(m_key, path, p_len); m_key[m_key_len] = '\0'; m_options_len = 0; if (*myoptions) m_options_len = strlen(myoptions) + 2; m_mapent_len = loc ? strlen(loc) : 0; if (m_mapent_len + m_options_len > MAPENT_MAX_LEN) { error(ap->logopt, MODPREFIX "multi mount mapent too long"); return CHE_FAIL; } if (*myoptions) { strcpy(m_mapent, "-"); strcat(m_mapent, myoptions); if (loc) { strcat(m_mapent, " "); if (loc) strcat(m_mapent, loc); } } else { if (loc) strcpy(m_mapent, loc); } ret = cache_update_offset(mc, name, m_key, m_mapent, age); if (ret == CHE_DUPLICATE) { warn(ap->logopt, MODPREFIX "syntax error or duplicate offset %s -> %s", path, loc); ret = CHE_OK; } else if (ret == CHE_FAIL) debug(ap->logopt, MODPREFIX "failed to update multi-mount offset %s -> %s", path, m_mapent); else { ret = CHE_OK; debug(ap->logopt, MODPREFIX "updated multi-mount offset %s -> %s", path, m_mapent); } return ret; } static int validate_location(unsigned int logopt, char *loc) { char *ptr = loc; /* We don't know much about these */ if (*ptr == ':') return 1; /* * If a ':/' is present now it must be a host name, except * for those special file systems like sshfs which use "#" * and "@" in the host name part and ipv6 addresses that * have ":", "[" and "]". */ if (!check_colon(ptr)) { char *esc; /* * Don't forget cases where a colon is present but * not followed by a "/" or, if there is no colon at * all, we don't know if it is actually invalid since * it may be a map name by itself, for example. */ if (!strchr(ptr, ':') || ((esc = strchr(ptr, '\\')) && *(esc + 1) == ':') || !strncmp(ptr, "file:", 5) || !strncmp(ptr, "yp:", 3) || !strncmp(ptr, "nis:", 4) || !strncmp(ptr, "nisplus:", 8) || !strncmp(ptr, "ldap:", 5) || !strncmp(ptr, "ldaps:", 6) || !strncmp(ptr, "sss:", 4) || !strncmp(ptr, "dir:", 4)) return 1; error(logopt, "expected colon delimeter not found in location %s", loc); return 0; } else { while (*ptr && strncmp(ptr, ":/", 2)) { if (!(isalnum(*ptr) || *ptr == '-' || *ptr == '.' || *ptr == '_' || *ptr == ',' || *ptr == '(' || *ptr == ')' || *ptr == '#' || *ptr == '@' || *ptr == ':' || *ptr == '[' || *ptr == ']' || *ptr == '%')) { error(logopt, "invalid character \"%c\" " "found in location %s", *ptr, loc); return 0; } ptr++; } if (*ptr && !strncmp(ptr, ":/", 2)) ptr++; } /* Must always be something following */ if (!*ptr) { error(logopt, "invalid location %s", loc); return 0; } return 1; } static int parse_mapent(const char *ent, char *g_options, char **options, char **location, int logopt) { char buf[MAX_ERR_BUF]; const char *p; char *myoptions, *loc; int l; p = ent; myoptions = strdup(g_options); if (!myoptions) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); error(logopt, MODPREFIX "strdup: %s", estr); return 0; } /* Local options are appended to per-map options */ if (*p == '-') { do { char *tmp, *newopt = NULL; p = parse_options(p, &newopt, logopt); if (newopt && strstr(newopt, myoptions)) { free(myoptions); myoptions = newopt; } else { tmp = concat_options(myoptions, newopt); if (!tmp) { char *estr; estr = strerror_r(errno, buf, MAX_ERR_BUF); error(logopt, MODPREFIX "concat_options: %s", estr); if (newopt) free(newopt); free(myoptions); return 0; } myoptions = tmp; } p = skipspace(p); } while (*p == '-'); } debug(logopt, MODPREFIX "gathered options: %s", myoptions); l = chunklen(p, check_colon(p)); loc = dequote(p, l, logopt); if (!loc) { if (strstr(myoptions, "fstype=autofs") && strstr(myoptions, "hosts")) { warn(logopt, MODPREFIX "possible missing location"); free(myoptions); return 0; } *options = myoptions; *location = NULL; return (p - ent); } /* Location can't begin with a '/' */ if (*p == '/') { warn(logopt, MODPREFIX "error location begins with \"/\""); free(myoptions); free(loc); return 0; } if (!validate_location(logopt, loc)) { free(myoptions); free(loc); return 0; } debug(logopt, MODPREFIX "dequote(\"%.*s\") -> %s", l, p, loc); p += l; p = skipspace(p); while (*p && ((*p == '"' && *(p + 1) != '/') || (*p != '"' && *p != '/'))) { char *tmp, *ent_chunk; l = chunklen(p, check_colon(p)); ent_chunk = dequote(p, l, logopt); if (!ent_chunk) { if (strstr(myoptions, "fstype=autofs") && strstr(myoptions, "hosts")) { warn(logopt, MODPREFIX "null location or out of memory"); free(myoptions); free(loc); return 0; } goto next; } /* Location can't begin with a '/' */ if (*p == '/') { warn(logopt, MODPREFIX "error location begins with \"/\""); free(ent_chunk); free(myoptions); free(loc); return 0; } if (!validate_location(logopt, ent_chunk)) { free(ent_chunk); free(myoptions); free(loc); return 0; } debug(logopt, MODPREFIX "dequote(\"%.*s\") -> %s", l, p, ent_chunk); tmp = realloc(loc, strlen(loc) + l + 2); if (!tmp) { error(logopt, MODPREFIX "out of memory"); free(ent_chunk); free(myoptions); free(loc); return 0; } loc = tmp; strcat(loc, " "); strcat(loc, ent_chunk); free(ent_chunk); next: p += l; p = skipspace(p); } *options = myoptions; *location = loc; return (p - ent); } static void cleanup_multi_triggers(struct autofs_point *ap, struct mapent *me, const char *root, int start, const char *base) { char path[PATH_MAX + 1]; char offset[PATH_MAX + 1]; char *poffset = offset; struct mapent *oe; struct list_head *mm_root, *pos; const char o_root[] = "/"; const char *mm_base; mm_root = &me->multi->multi_list; if (!base) mm_base = o_root; else mm_base = base; pos = NULL; /* Make sure "none" of the offsets have an active mount. */ while ((poffset = cache_get_offset(mm_base, poffset, start, mm_root, &pos))) { oe = cache_lookup_offset(mm_base, poffset, start, &me->multi_list); /* root offset is a special case */ if (!oe || !oe->mapent || (strlen(oe->key) - start) == 1) continue; strcpy(path, root); strcat(path, poffset); if (umount(path)) { error(ap->logopt, "error recovering from mount fail"); error(ap->logopt, "cannot umount offset %s", path); } } return; } static int mount_subtree(struct autofs_point *ap, struct mapent *me, const char *name, char *loc, char *options, void *ctxt) { struct mapent *mm; struct mapent *ro; char *mm_root, *mm_base, *mm_key; const char *mnt_root; unsigned int mm_root_len, mnt_root_len; int start, ret = 0, rv; rv = 0; mm = me->multi; mm_key = mm->key; if (*mm_key == '/') { mm_root = mm_key; start = strlen(mm_key); } else { start = strlen(ap->path) + strlen(mm_key) + 1; mm_root = alloca(start + 3); strcpy(mm_root, ap->path); strcat(mm_root, "/"); strcat(mm_root, mm_key); } mm_root_len = strlen(mm_root); mnt_root = mm_root; mnt_root_len = mm_root_len; if (me == me->multi) { /* name = NULL */ /* destination = mm_root */ mm_base = "/"; /* Mount root offset if it exists */ ro = cache_lookup_offset(mm_base, mm_base, strlen(mm_root), &me->multi_list); if (ro) { char *myoptions, *ro_loc, *tmp; int namelen = name ? strlen(name) : 0; const char *root; int ro_len; myoptions = NULL; ro_loc = NULL; rv = parse_mapent(ro->mapent, options, &myoptions, &ro_loc, ap->logopt); if (!rv) { warn(ap->logopt, MODPREFIX "failed to parse root offset"); cache_delete_offset_list(me->mc, name); return 1; } ro_len = 0; if (ro_loc) ro_len = strlen(ro_loc); tmp = alloca(mnt_root_len + 2); strcpy(tmp, mnt_root); tmp[mnt_root_len] = '/'; tmp[mnt_root_len + 1] = '\0'; root = tmp; rv = sun_mount(ap, root, name, namelen, ro_loc, ro_len, myoptions, ctxt); free(myoptions); if (ro_loc) free(ro_loc); } if (ro && rv == 0) { ret = mount_multi_triggers(ap, me, mnt_root, start, mm_base); if (ret == -1) { error(ap->logopt, MODPREFIX "failed to mount offset triggers"); cleanup_multi_triggers(ap, me, mnt_root, start, mm_base); return 1; } } else if (rv <= 0) { ret = mount_multi_triggers(ap, me, mm_root, start, mm_base); if (ret == -1) { error(ap->logopt, MODPREFIX "failed to mount offset triggers"); cleanup_multi_triggers(ap, me, mm_root, start, mm_base); return 1; } } } else { int loclen = strlen(loc); int namelen = strlen(name); mnt_root = name; /* name = mm_root + mm_base */ /* destination = mm_root + mm_base = name */ mm_base = &me->key[start]; rv = sun_mount(ap, mnt_root, name, namelen, loc, loclen, options, ctxt); if (rv == 0) { ret = mount_multi_triggers(ap, me->multi, mnt_root, start, mm_base); if (ret == -1) { error(ap->logopt, MODPREFIX "failed to mount offset triggers"); cleanup_multi_triggers(ap, me, mnt_root, start, mm_base); return 1; } } else if (rv < 0) { char *mm_root_base = alloca(strlen(mm_root) + strlen(mm_base) + 1); strcpy(mm_root_base, mm_root); strcat(mm_root_base, mm_base); ret = mount_multi_triggers(ap, me->multi, mm_root_base, start, mm_base); if (ret == -1) { error(ap->logopt, MODPREFIX "failed to mount offset triggers"); cleanup_multi_triggers(ap, me, mm_root, start, mm_base); return 1; } } } /* Mount for base of tree failed */ if (rv > 0) return rv; /* * Convert fail on nonstrict, non-empty multi-mount * to success */ if (rv < 0 && ret > 0) rv = 0; return rv; } static char *do_expandsunent(const char *src, const char *key, const struct substvar *svc, int slashify_colons) { char *mapent; int len; len = expandsunent(src, NULL, key, svc, slashify_colons); if (len == 0) { errno = EINVAL; return NULL; } len++; mapent = malloc(len); if (!mapent) return NULL; memset(mapent, 0, len); expandsunent(src, mapent, key, svc, slashify_colons); return mapent; } /* * syntax is: * [-options] location [location] ... * [-options] [mountpoint [-options] location [location] ... ]... * * There are three ways this routine can be called. One where we parse * offsets in a multi-mount entry adding them to the cache for later lookups. * Another where we parse a multi-mount entry looking for a root offset mount * and mount it if it exists and also mount its offsets down to the first * level nexting point. Finally to mount non multi-mounts and to mount a * lower level multi-mount nesting point and its offsets. */ int parse_mount(struct autofs_point *ap, const char *name, int name_len, const char *mapent, void *context) { struct parse_context *ctxt = (struct parse_context *) context; char buf[MAX_ERR_BUF]; struct map_source *source; struct mapent_cache *mc; struct mapent *me; char *pmapent, *options; const char *p; int mapent_len, rv = 0; int cur_state; int slashify = ctxt->slashify_colons; unsigned int append_options; source = ap->entry->current; ap->entry->current = NULL; master_source_current_signal(ap->entry); mc = source->mc; if (!mapent) { warn(ap->logopt, MODPREFIX "error: empty map entry"); return 1; } pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); /* Offset map entries have been expanded already, avoid expanding * them again so that the quote handling is consistent between map * entry locations and (previously expanded) offset map entry * locations. */ if (*name == '/') { cache_readlock(mc); me = cache_lookup_distinct(mc, name); if (me && me->multi && me->multi != me) { cache_unlock(mc); mapent_len = strlen(mapent) + 1; pmapent = malloc(mapent_len + 1); if (!pmapent) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); logerr(MODPREFIX "malloc: %s", estr); return 1; } memset(pmapent, 0, mapent_len + 1); strcpy(pmapent, mapent); goto dont_expand; } cache_unlock(mc); } macro_lock(); ctxt->subst = addstdenv(ctxt->subst, NULL); pmapent = do_expandsunent(mapent, name, ctxt->subst, slashify); if (!pmapent) { error(ap->logopt, MODPREFIX "failed to expand map entry"); ctxt->subst = removestdenv(ctxt->subst, NULL); macro_unlock(); pthread_setcancelstate(cur_state, NULL); return 1; } mapent_len = strlen(pmapent) + 1; ctxt->subst = removestdenv(ctxt->subst, NULL); macro_unlock(); dont_expand: pthread_setcancelstate(cur_state, NULL); debug(ap->logopt, MODPREFIX "expanded entry: %s", pmapent); append_options = defaults_get_append_options(); options = strdup(ctxt->optstr ? ctxt->optstr : ""); if (!options) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); free(pmapent); logerr(MODPREFIX "strdup: %s", estr); return 1; } p = skipspace(pmapent); /* Deal with 0 or more options */ if (*p == '-') { char *tmp, *mnt_options = NULL; do { char *noptions = NULL; p = parse_options(p, &noptions, ap->logopt); if (mnt_options && noptions && strstr(noptions, mnt_options)) { free(mnt_options); mnt_options = noptions; } else { tmp = concat_options(mnt_options, noptions); if (!tmp) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); error(ap->logopt, MODPREFIX "concat_options: %s", estr); if (noptions) free(noptions); if (mnt_options) free(mnt_options); free(options); free(pmapent); return 1; } mnt_options = tmp; } p = skipspace(p); } while (*p == '-'); if (options && !append_options) { free(options); options = NULL; } if (append_options) { if (options && mnt_options && strstr(mnt_options, options)) { free(options); options = mnt_options; } else { tmp = concat_options(options, mnt_options); if (!tmp) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); error(ap->logopt, MODPREFIX "concat_options: %s", estr); if (options) free(options); if (mnt_options) free(mnt_options); free(pmapent); return 1; } options = tmp; } } else options = mnt_options; } debug(ap->logopt, MODPREFIX "gathered options: %s", options); if (check_is_multi(p)) { char *m_root = NULL; int m_root_len; time_t age; int l; /* If name starts with "/" it's a direct mount */ if (*name == '/') { m_root_len = name_len; m_root = alloca(m_root_len + 1); if (!m_root) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); free(options); free(pmapent); logerr(MODPREFIX "alloca: %s", estr); return 1; } strcpy(m_root, name); } else { m_root_len = strlen(ap->path) + name_len + 1; m_root = alloca(m_root_len + 1); if (!m_root) { char *estr = strerror_r(errno, buf, MAX_ERR_BUF); free(options); free(pmapent); logerr(MODPREFIX "alloca: %s", estr); return 1; } strcpy(m_root, ap->path); strcat(m_root, "/"); strcat(m_root, name); } pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cur_state); cache_readlock(mc); me = cache_lookup_distinct(mc, name); if (!me) { free(options); free(pmapent); cache_unlock(mc); pthread_setcancelstate(cur_state, NULL); error(ap->logopt, MODPREFIX "can't find multi root %s", name); return 1; } cache_multi_writelock(me); /* So we know we're the multi-mount root */ if (!me->multi) me->multi = me; else { /* * The amd host mount type assumes the lookup name * is the host name for the host mount but amd uses * ${rhost} for this. * * This introduces the possibility of multiple * concurrent mount requests since constructing a * mount tree that isn't under the lookup name can't * take advantage of the kernel queuing of other * concurrent lookups while the mount tree is * constructed. * * Consequently multi-mount updates (currently only * done for the internal hosts map which the amd * parser also uses for its hosts map) can't be * allowed for amd mounts. */ if (source->flags & MAP_FLAG_FORMAT_AMD) { free(options); free(pmapent); cache_multi_unlock(me); cache_unlock(mc); pthread_setcancelstate(cur_state, NULL); return 0; } } age = me->age; /* It's a multi-mount; deal with it */ do { char *path, *myoptions, *loc; int status; if ((*p == '"' && *(p + 1) != '/') || (*p != '"' && *p != '/')) { l = 0; path = dequote("/", 1, ap->logopt); debug(ap->logopt, MODPREFIX "dequote(\"/\") -> %s", path); } else { l = span_space(p, mapent_len - (p - pmapent)); path = sanitize_path(p, l, LKP_MULTI, ap->logopt); debug(ap->logopt, MODPREFIX "dequote(\"%.*s\") -> %s", l, p, path); } if (!path) { warn(ap->logopt, MODPREFIX "null path or out of memory"); cache_delete_offset_list(mc, name); cache_multi_unlock(me); cache_unlock(mc); free(options); free(pmapent); pthread_setcancelstate(cur_state, NULL); return 1; } p += l; p = skipspace(p); myoptions = NULL; loc = NULL; l = parse_mapent(p, options, &myoptions, &loc, ap->logopt); if (!l) { cache_delete_offset_list(mc, name); cache_multi_unlock(me); cache_unlock(mc); free(path); free(options); free(pmapent); pthread_setcancelstate(cur_state, NULL); return 1; } p += l; p = skipspace(p); master_source_current_wait(ap->entry); ap->entry->current = source; status = update_offset_entry(ap, name, m_root, m_root_len, path, myoptions, loc, age); if (status != CHE_OK) { warn(ap->logopt, MODPREFIX "error adding multi-mount"); cache_delete_offset_list(mc, name); cache_multi_unlock(me); cache_unlock(mc); free(path); free(options); free(pmapent); free(myoptions); if (loc) free(loc); pthread_setcancelstate(cur_state, NULL); return 1; } if (loc) free(loc); free(path); free(myoptions); } while (*p == '/' || (*p == '"' && *(p + 1) == '/')); /* * We've got the ordered list of multi-mount entries so go * through and remove any stale entries if this is the top * of the multi-mount and set the parent entry of each. */ if (me == me->multi) clean_stale_multi_triggers(ap, me, NULL, NULL); cache_set_parents(me); rv = mount_subtree(ap, me, name, NULL, options, ctxt); cache_multi_unlock(me); cache_unlock(mc); free(options); free(pmapent); pthread_setcancelstate(cur_state, NULL); return rv; } else { /* Normal (and non-root multi-mount) entries */ char *loc; int loclen; int l; /* * If this is an offset belonging to a multi-mount entry * it's already been parsed (above) and any option string * has already been stripped so just use the remainder. */ cache_readlock(mc); if (*name == '/' && (me = cache_lookup_distinct(mc, name)) && me->multi) { loc = strdup(p); if (!loc) { free(options); free(pmapent); cache_unlock(mc); warn(ap->logopt, MODPREFIX "out of memory"); return 1; } cache_multi_writelock(me); rv = mount_subtree(ap, me, name, loc, options, ctxt); cache_multi_unlock(me); cache_unlock(mc); free(loc); free(options); free(pmapent); return rv; } cache_unlock(mc); l = chunklen(p, check_colon(p)); loc = dequote(p, l, ap->logopt); if (!loc) { free(options); free(pmapent); warn(ap->logopt, MODPREFIX "null location or out of memory"); return 1; } /* Location can't begin with a '/' */ if (*p == '/') { free(options); free(pmapent); free(loc); warn(ap->logopt, MODPREFIX "error location begins with \"/\""); return 1; } if (!validate_location(ap->logopt, loc)) { free(loc); free(options); free(pmapent); return 1; } debug(ap->logopt, MODPREFIX "dequote(\"%.*s\") -> %s", l, p, loc); p += l; p = skipspace(p); while (*p) { char *tmp, *ent; l = chunklen(p, check_colon(p)); ent = dequote(p, l, ap->logopt); if (!ent) { free(loc); free(options); free(pmapent); warn(ap->logopt, MODPREFIX "null location or out of memory"); return 1; } if (!validate_location(ap->logopt, ent)) { free(ent); free(loc); free(options); free(pmapent); return 1; } debug(ap->logopt, MODPREFIX "dequote(\"%.*s\") -> %s", l, p, ent); tmp = realloc(loc, strlen(loc) + l + 2); if (!tmp) { free(ent); free(loc); free(options); free(pmapent); error(ap->logopt, MODPREFIX "out of memory"); return 1; } loc = tmp; strcat(loc, " "); strcat(loc, ent); free(ent); p += l; p = skipspace(p); } /* * If options are asking for a hosts map loc should be * NULL but we see it can contain junk, so .... */ if ((strstr(options, "fstype=autofs") && strstr(options, "hosts"))) { if (loc) { free(loc); loc = NULL; } loclen = 0; } else { loclen = strlen(loc); if (loclen == 0) { free(loc); free(options); free(pmapent); error(ap->logopt, MODPREFIX "entry %s is empty!", name); return 1; } } debug(ap->logopt, MODPREFIX "core of entry: options=%s, loc=%.*s", options, loclen, loc); if (!strcmp(ap->path, "/-")) rv = sun_mount(ap, name, name, name_len, loc, loclen, options, ctxt); else rv = sun_mount(ap, ap->path, name, name_len, loc, loclen, options, ctxt); if (loc) free(loc); free(options); free(pmapent); pthread_setcancelstate(cur_state, NULL); } return rv; } int parse_done(void *context) { int rv = 0; struct parse_context *ctxt = (struct parse_context *) context; instance_mutex_lock(); if (--init_ctr == 0) { rv = close_mount(mount_nfs); mount_nfs = NULL; } instance_mutex_unlock(); if (ctxt) kill_context(ctxt); return rv; }