autofs-5.1.7 - add mount and umount offsets functions From: Ian Kent Add tree_mapent_mount_offsets() and tree_mapent_umount_offsets() to the mapent tree handling implementation. Signed-off-by: Ian Kent --- CHANGELOG | 1 include/mounts.h | 2 lib/mounts.c | 260 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 263 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 0bd6f181..892f7581 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -39,6 +39,7 @@ - fix mount_fullpath(). - add tree_mapent_cleanup_offsets(). - add set_offset_tree_catatonic(). +- add mount and umount offsets functions. 25/01/2021 autofs-5.1.7 - make bind mounts propagation slave by default. diff --git a/include/mounts.h b/include/mounts.h index 5441ee0e..e56f80ba 100644 --- a/include/mounts.h +++ b/include/mounts.h @@ -172,6 +172,8 @@ struct tree_node *tree_mapent_root(struct mapent *me); int tree_mapent_add_node(struct mapent_cache *mc, const char *base, const char *key); int tree_mapent_delete_offsets(struct mapent_cache *mc, const char *key); void tree_mapent_cleanup_offsets(struct mapent *oe); +int tree_mapent_mount_offsets(struct mapent *oe, int nonstrict); +int tree_mapent_umount_offsets(struct mapent *oe, int nonstrict); int unlink_mount_tree(struct autofs_point *ap, const char *mp); void free_mnt_list(struct mnt_list *list); int is_mounted(const char *mp, unsigned int type); diff --git a/lib/mounts.c b/lib/mounts.c index f075a27e..f7c29475 100644 --- a/lib/mounts.c +++ b/lib/mounts.c @@ -1692,6 +1692,266 @@ void tree_mapent_cleanup_offsets(struct mapent *oe) } } +static int tree_mapent_rmdir_path_offset(struct autofs_point *ap, struct mapent *oe) +{ + struct mapent *mm_root = MAPENT(MAPENT_ROOT(oe)); + char *dir, *path; + unsigned int split; + int ret; + + if (ap->type == LKP_DIRECT) + return rmdir_path(ap, oe->key, mm_root->dev); + + dir = strdup(oe->key); + + if (ap->flags & MOUNT_FLAG_GHOST) + split = ap->len + mm_root->len + 1; + else + split = ap->len; + + dir[split] = '\0'; + path = &dir[split + 1]; + + if (chdir(dir) == -1) { + error(ap->logopt, "failed to chdir to %s", dir); + free(dir); + return -1; + } + + ret = rmdir_path(ap, path, ap->dev); + + free(dir); + + if (chdir("/") == -1) + error(ap->logopt, "failed to chdir to /"); + + return ret; +} + +static int tree_mapent_mount_offset(struct mapent *oe, void *ptr) +{ + struct traverse_subtree_context *ctxt = ptr; + struct autofs_point *ap = ctxt->ap; + int ret; + + debug(ap->logopt, "mount offset %s", oe->key); + + ret = mount_autofs_offset(ap, oe); + if (ret < MOUNT_OFFSET_OK) { + if (ret != MOUNT_OFFSET_IGNORE) { + warn(ap->logopt, "failed to mount offset"); + return 0; + } else { + debug(ap->logopt, + "ignoring \"nohide\" trigger %s", oe->key); + /* + * Ok, so we shouldn't modify the mapent but + * mount requests are blocked at a point above + * this and expire only uses the mapent key or + * holds the cache write lock. + */ + free(oe->mapent); + oe->mapent = NULL; + } + } + + return 1; +} + +static int tree_mapent_umount_offset(struct mapent *oe, void *ptr) +{ + struct traverse_subtree_context *ctxt = ptr; + struct autofs_point *ap = ctxt->ap; + int ret = 1; + + /* + * Check for and umount subtree offsets resulting from + * nonstrict mount fail. + */ + ret = tree_mapent_umount_offsets(oe, ctxt->strict); + if (!ret) + return 0; + + /* + * If an offset that has an active mount has been removed + * from the multi-mount we don't want to attempt to trigger + * mounts for it. Obviously this is because it has been + * removed, but less obvious is the potential strange + * behaviour that can result if we do try and mount it + * again after it's been expired. For example, if an NFS + * file system is no longer exported and is later umounted + * it can be mounted again without any error message but + * shows as an empty directory. That's going to confuse + * people for sure. + * + * If the mount cannot be umounted (the process is now + * using a stale mount) the offset needs to be invalidated + * so no further mounts will be attempted but the offset + * cache entry must remain so expires can continue to + * attempt to umount it. If the mount can be umounted and + * the offset is removed, at least for NFS we will get + * ESTALE errors when attempting list the directory. + */ + if (oe->ioctlfd != -1 || + is_mounted(oe->key, MNTS_REAL)) { + if (umount_ent(ap, oe->key) && + is_mounted(oe->key, MNTS_REAL)) { + debug(ap->logopt, + "offset %s has active mount, invalidate", + oe->key); + /* + * Ok, so we shouldn't modify the mapent but + * mount requests are blocked at a point above + * this and expire only uses the mapent key or + * holds the cache write lock. + */ + if (oe->mapent) { + free(oe->mapent); + oe->mapent = NULL; + } + return 0; + } + } + + /* Don't bother if there's noting to umount. */ + if (!is_mounted(oe->key, MNTS_AUTOFS)) + goto done; + + debug(ap->logopt, "umount offset %s", oe->key); + + if (umount_autofs_offset(ap, oe)) { + warn(ap->logopt, "failed to umount offset"); + ret = 0; + } else { + struct stat st; + int ret; + + if (!(oe->flags & MOUNT_FLAG_DIR_CREATED)) + goto done; + + /* + * An error due to partial directory removal is + * ok so only try and remount the offset if the + * actual mount point still exists. + */ + ret = tree_mapent_rmdir_path_offset(ap, oe); + if (ret == -1 && !stat(oe->key, &st)) { + ret = tree_mapent_mount_offset(oe, ctxt); + /* But we did origianlly create this */ + oe->flags |= MOUNT_FLAG_DIR_CREATED; + } + } +done: + return ret; +} + +static int tree_mapent_mount_offsets_work(struct tree_node *n, void *ptr) +{ + struct traverse_subtree_context *ctxt = ptr; + struct mapent *oe = MAPENT(n); + struct mapent *mm_root = MAPENT(MAPENT_ROOT(oe)); + struct autofs_point *ap = ctxt->ap; + int ret; + + if (!oe->mapent) + return 1; + + /* Stale offset, no longer present in the mapent */ + if (oe->age != mm_root->age) { + /* Best effort */ + tree_mapent_umount_offset(oe, ctxt); + return 1; + } + + ret = tree_mapent_mount_offset(oe, ctxt); + + /* + * If re-constructing a multi-mount it's necessary to walk + * into nested mounts, unlike the usual "mount only what's + * needed as you go" behavior. + */ + if (ap->state == ST_READMAP && ap->flags & MOUNT_FLAG_REMOUNT) { + if (oe->ioctlfd != -1 || + is_mounted(oe->key, MNTS_REAL)) + /* Best effort */ + tree_mapent_mount_offsets(oe, !ctxt->strict); + } + + return ret; +} + +int tree_mapent_mount_offsets(struct mapent *oe, int nonstrict) +{ + struct tree_node *base = MAPENT_NODE(oe); + struct traverse_subtree_context ctxt = { + .ap = oe->mc->ap, + .base = base, + .strict = !nonstrict, + }; + + return tree_mapent_traverse_subtree(base, + tree_mapent_mount_offsets_work, &ctxt); +} + +static int tree_mapent_umount_offsets_work(struct tree_node *n, void *ptr) +{ + struct mapent *oe = MAPENT(n); + + return tree_mapent_umount_offset(oe, ptr); +} + +int tree_mapent_umount_offsets(struct mapent *oe, int nonstrict) +{ + struct tree_node *base = MAPENT_NODE(oe); + struct autofs_point *ap = oe->mc->ap; + struct traverse_subtree_context ctxt = { + .ap = ap, + .base = base, + .strict = !nonstrict, + }; + int ret; + + ret = tree_mapent_traverse_subtree(base, + tree_mapent_umount_offsets_work, &ctxt); + if (ret && tree_mapent_is_root(oe)) { + char mp[PATH_MAX + 1]; + + /* + * The map entry cache stores mapent keys. For indirect + * mount maps they are single direcory components so when + * one of these keys is the root of a multi-mount the mount + * path must be constructed. + */ + if (!mount_fullpath(mp, PATH_MAX, ap->path, oe->key)) { + error(ap->logopt, "mount path is too long"); + return 0; + } + + /* + * Special case. + * If we can't umount the root container then we can't + * delete the offsets from the cache and we need to put + * the offset triggers back. + */ + if (is_mounted(mp, MNTS_REAL)) { + info(ap->logopt, "unmounting dir = %s", mp); + if (umount_ent(ap, mp) && + is_mounted(mp, MNTS_REAL)) { + if (!tree_mapent_mount_offsets(oe, 1)) + warn(ap->logopt, + "failed to remount offset triggers"); + return 0; + } + } + + /* check for mounted mount entry and remove it if found */ + mnts_remove_mount(mp, MNTS_MOUNTED); + + } + + return ret; +} + /* From glibc decode_name() */ /* Since the values in a line are separated by spaces, a name cannot * contain a space. Therefore some programs encode spaces in names