From fdef42e82d66011a3a92c9c96db4ada2fa8d4814 Mon Sep 17 00:00:00 2001 From: jiffin Date: Tue, 4 Nov 2014 20:33:01 +0530 Subject: gNFS : make it possible to mount a subdir that actually is a symlink We are using the function to export all sub-directories in a gluster volume via nfs. For real directories it works fine but if we have a symbolic link which points to the directory, it is not possible to mount that directory via nfs and the nameof the link. Kernel nfs resolves symlink handle to directoryhandle , similar gluster nfs should resolve the symbolic link handle into directory handle. Change-Id: I8bd07534ba9474f0b863f2335b2fd222ab625dba BUG: 1157223 Signed-off-by: jiffin tony thottan Reviewed-on: http://review.gluster.org/9052 Reviewed-by: soumya k Tested-by: Gluster Build System Reviewed-by: Jeff Darcy Reviewed-by: Niels de Vos --- libglusterfs/src/common-utils.c | 127 ++++++++++++++++++++++++++ libglusterfs/src/common-utils.h | 4 + tests/bugs/bug-1157223-symlink-mounting.t | 124 +++++++++++++++++++++++++ xlators/nfs/server/src/mount3.c | 144 +++++++++++++++++++++++++++++- 4 files changed, 398 insertions(+), 1 deletion(-) create mode 100644 tests/bugs/bug-1157223-symlink-mounting.t diff --git a/libglusterfs/src/common-utils.c b/libglusterfs/src/common-utils.c index 0f1aceec39b..1318c4d49a6 100644 --- a/libglusterfs/src/common-utils.c +++ b/libglusterfs/src/common-utils.c @@ -3429,3 +3429,130 @@ fop_log_level (glusterfs_fop_t fop, int op_errno) return GF_LOG_ERROR; } + +/* This function will build absolute path of file/directory from the + * current location and relative path given from the current location + * For example consider our current path is /a/b/c/ and relative path + * from current location is ./../x/y/z .After parsing through this + * function the absolute path becomes /a/b/x/y/z/. + * + * The function gives a pointer to absolute path if it is successful + * and also returns zero. + * Otherwise function gives NULL pointer with returning an err value. + * + * So the user need to free memory allocated for path. + * + */ + +int32_t +gf_build_absolute_path (char *current_path, char *relative_path, char **path) +{ + char *absolute_path = NULL; + char *token = NULL; + char *component = NULL; + char *saveptr = NULL; + char *end = NULL; + int ret = 0; + size_t relativepath_len = 0; + size_t currentpath_len = 0; + size_t max_absolutepath_len = 0; + + GF_ASSERT (current_path); + GF_ASSERT (relative_path); + GF_ASSERT (path); + + if (!path || !current_path || !relative_path) { + ret = -EFAULT; + goto err; + } + /* Check for current and relative path + * current path should be absolute one and start from '/' + * relative path should not start from '/' + */ + currentpath_len = strlen (current_path); + if (current_path[0] != '/' || (currentpath_len > PATH_MAX)) { + gf_log (THIS->name, GF_LOG_ERROR, "Wrong value for" + " current path %s", current_path); + ret = -EINVAL; + goto err; + } + + relativepath_len = strlen (relative_path); + if (relative_path[0] == '/' || (relativepath_len > PATH_MAX)) { + gf_log (THIS->name, GF_LOG_ERROR, "Wrong value for" + " relative path %s", relative_path); + ret = -EINVAL; + goto err; + } + + /* It is maximum possible value for absolute path */ + max_absolutepath_len = currentpath_len + relativepath_len + 2; + + absolute_path = GF_CALLOC (1, max_absolutepath_len, gf_common_mt_char); + if (!absolute_path) { + ret = -ENOMEM; + goto err; + } + absolute_path[0] = '\0'; + + /* If current path is root i.e contains only "/", we do not + * need to copy it + */ + if (strcmp (current_path, "/") != 0) { + strcpy (absolute_path, current_path); + + /* We trim '/' at the end for easier string manipulation */ + gf_path_strip_trailing_slashes (absolute_path); + } + + /* Used to spilt relative path based on '/' */ + component = gf_strdup (relative_path); + if (!component) { + ret = -ENOMEM; + goto err; + } + + /* In the relative path, we want to consider ".." and "." + * if token is ".." , we just need to reduce one level hierarchy + * if token is "." , we just ignore it + * if token is NULL , end of relative path + * if absolute path becomes '\0' and still "..", then it is a bad + * relative path, it points to out of boundary area and stop + * building the absolute path + * All other cases we just concatenate token to the absolute path + */ + for (token = strtok_r (component, "/", &saveptr), + end = strchr (absolute_path, '\0'); token; + token = strtok_r (NULL, "/", &saveptr)) { + if (strcmp (token, ".") == 0) + continue; + + else if (strcmp (token, "..") == 0) { + + if (absolute_path[0] == '\0') { + ret = -EACCES; + goto err; + } + + end = strrchr (absolute_path, '/'); + *end = '\0'; + } else { + ret = snprintf (end, max_absolutepath_len - + strlen (absolute_path), "/%s", token); + end = strchr (absolute_path , '\0'); + } + } + + if (strlen (absolute_path) > PATH_MAX) { + ret = -EINVAL; + goto err; + } + *path = gf_strdup (absolute_path); + +err: + if (component) + GF_FREE (component); + if (absolute_path) + GF_FREE (absolute_path); + return ret; +} diff --git a/libglusterfs/src/common-utils.h b/libglusterfs/src/common-utils.h index 0d5abb42ec2..d2bc42b9662 100644 --- a/libglusterfs/src/common-utils.h +++ b/libglusterfs/src/common-utils.h @@ -653,4 +653,8 @@ gf_backtrace_done (char *buf); gf_loglevel_t fop_log_level (glusterfs_fop_t fop, int op_errno); + +int32_t +gf_build_absolute_path (char *current_path, char *relative_path, char **path); + #endif /* _COMMON_UTILS_H */ diff --git a/tests/bugs/bug-1157223-symlink-mounting.t b/tests/bugs/bug-1157223-symlink-mounting.t new file mode 100644 index 00000000000..4ebc3453889 --- /dev/null +++ b/tests/bugs/bug-1157223-symlink-mounting.t @@ -0,0 +1,124 @@ +#!/bin/bash + +. $(dirname $0)/../include.rc +. $(dirname $0)/../nfs.rc + +cleanup; + +## Start and create a volume +TEST glusterd +TEST pidof glusterd + +TEST $CLI volume info; +TEST $CLI volume create $V0 $H0:$B0/$V0 + +TEST $CLI volume start $V0; + +## Wait for volume to register with rpc.mountd +EXPECT_WITHIN $NFS_EXPORT_TIMEOUT "1" is_nfs_export_available; + +## Mount NFS +TEST mount_nfs $H0:/$V0 $N0 nolock; + +mkdir $N0/dir1; +mkdir $N0/dir2; +pushd $N0/ ; + +##link created using relative path +ln -s dir1 symlink1; + +##relative path contains ".." +ln -s ../dir1 dir2/symlink2; + +##link created using absolute path +ln -s $N0/dir1 symlink3; + +##link pointing to another symlinks +ln -s symlink1 symlink4 +ln -s symlink3 symlink5 + +##dead links +ln -s does/not/exist symlink6 + +##link which contains ".." points out of glusterfs +ln -s ../../ symlink7 + +##links pointing to unauthorized area +ln -s .glusterfs symlink8 + +popd ; + +##Umount the volume +EXPECT_WITHIN $UMOUNT_TIMEOUT "Y" umount_nfs $N0 + +## Mount and umount NFS via directory +TEST mount_nfs $H0:/$V0/dir1 $N0 nolock; +EXPECT_WITHIN $UMOUNT_TIMEOUT "Y" umount_nfs $N0 + +## Mount and umount NFS via symlink1 +TEST mount_nfs $H0:/$V0/symlink1 $N0 nolock; +EXPECT_WITHIN $UMOUNT_TIMEOUT "Y" umount_nfs $N0 + +## Mount and umount NFS via symlink2 +TEST mount_nfs $H0:/$V0/dir2/symlink2 $N0 nolock; +EXPECT_WITHIN $UMOUNT_TIMEOUT "Y" umount_nfs $N0 + +## Mount NFS via symlink3 should fail +TEST ! mount_nfs $H0:/$V0/symlink3 $N0 nolock; + +## Mount and umount NFS via symlink4 +TEST mount_nfs $H0:/$V0/symlink4 $N0 nolock; +EXPECT_WITHIN $UMOUNT_TIMEOUT "Y" umount_nfs $N0 + +## Mount NFS via symlink5 should fail +TEST ! mount_nfs $H0:/$V0/symlink5 $N0 nolock; + +## Mount NFS via symlink6 should fail +TEST ! mount_nfs $H0:/$V0/symlink6 $N0 nolock; + +## Mount NFS via symlink7 should fail +TEST ! mount_nfs $H0:/$V0/symlink7 $N0 nolock; + +## Mount NFS via symlink8 should fail +TEST ! mount_nfs $H0:/$V0/symlink8 $N0 nolock; + +##Similar check for udp mount +$CLI volume stop $V0 +TEST $CLI volume set $V0 nfs.mount-udp on +$CLI volume start $V0 + +## Wait for volume to register with rpc.mountd +EXPECT_WITHIN $NFS_EXPORT_TIMEOUT "1" is_nfs_export_available; + +## Mount and umount NFS via directory +TEST mount_nfs $H0:/$V0/dir1 $N0 nolock,mountproto=udp,proto=tcp; +EXPECT_WITHIN $UMOUNT_TIMEOUT "Y" umount_nfs $N0 + +## Mount and umount NFS via symlink1 +TEST mount_nfs $H0:/$V0/symlink1 $N0 nolock,mountproto=udp,proto=tcp; +EXPECT_WITHIN $UMOUNT_TIMEOUT "Y" umount_nfs $N0 + +## Mount and umount NFS via symlink2 +TEST mount_nfs $H0:/$V0/dir2/symlink2 $N0 nolock,mountproto=udp,proto=tcp; +EXPECT_WITHIN $UMOUNT_TIMEOUT "Y" umount_nfs $N0 + +## Mount NFS via symlink3 should fail +TEST ! mount_nfs $H0:/$V0/symlink3 $N0 nolock,mountproto=udp,proto=tcp; + +## Mount and umount NFS via symlink4 +TEST mount_nfs $H0:/$V0/symlink4 $N0 nolock,mountproto=udp,proto=tcp; +EXPECT_WITHIN $UMOUNT_TIMEOUT "Y" umount_nfs $N0 + +## Mount NFS via symlink5 should fail +TEST ! mount_nfs $H0:/$V0/symlink5 $N0 nolock,mountproto=udp,proto=tcp; + +## Mount NFS via symlink6 should fail +TEST ! mount_nfs $H0:/$V0/symlink6 $N0 nolock,mountproto=udp,proto=tcp; + +##symlink7 is not check here, because in udp mount ../../ resolves into root '/' + +## Mount NFS via symlink8 should fail +TEST ! mount_nfs $H0:/$V0/symlink8 $N0 nolock,mountproto=udp,proto=tcp; + +rm -rf $H0:$B0/ +cleanup; diff --git a/xlators/nfs/server/src/mount3.c b/xlators/nfs/server/src/mount3.c index 6be856ba92a..f76c1c41ff2 100644 --- a/xlators/nfs/server/src/mount3.c +++ b/xlators/nfs/server/src/mount3.c @@ -845,6 +845,15 @@ mnt3_resolve_subdir_cbk (call_frame_t *frame, void *cookie, xlator_t *this, struct iatt *buf, dict_t *xattr, struct iatt *postparent); +int +mnt3_parse_dir_exports (rpcsvc_request_t *req, struct mount3_state *ms, + char *subdir); + +int32_t +mnt3_readlink_cbk (call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, const char *path, + struct iatt *buf, dict_t *xdata); + /* There are multiple components in the directory export path and each one * needs to be looked up one after the other. */ @@ -880,6 +889,13 @@ __mnt3_resolve_export_subdir_comp (mnt3_resolve_t *mres) } nfs_request_user_init (&nfu, mres->req); + if (IA_ISLNK (mres->resolveloc.inode->ia_type)) { + ret = nfs_readlink (mres->mstate->nfsx, mres->exp->vol, &nfu, + &mres->resolveloc, mnt3_readlink_cbk, mres); + gf_log (GF_MNT, GF_LOG_DEBUG, "Symlink found , need to resolve" + " into directory handle"); + goto err; + } ret = nfs_lookup (mres->mstate->nfsx, mres->exp->vol, &nfu, &mres->resolveloc, mnt3_resolve_subdir_cbk, mres); @@ -954,7 +970,124 @@ err: return 0; } +/* This function resolves symbolic link into directory path from + * the mount and restart the parsing process from the begining + * + * Note : Path specified in the symlink should be relative to the + * symlink, because that is the one which is consistent throught + * out the file system. + * If the symlink resolves into another symlink ,then same process + * will be repeated. + * If symbolic links points outside the file system are not considered + * here. + * + * TODO : 1.) This function cannot handle symlinks points to path which + * goes out of the filesystem and comes backs again to same. + * For example, consider vol is exported volume.It contains + * dir, + * symlink1 which points to ../vol/dir, + * symlink2 which points to ../mnt/../vol/dir, + * symlink1 and symlink2 are not handled right now. + * + * 2.) udp mount routine is much simpler from tcp routine and resolves + * symlink directly.May be ,its better we change this routine + * similar to udp + */ +int32_t +mnt3_readlink_cbk (call_frame_t *frame, void *cookie, xlator_t *this, + int32_t op_ret, int32_t op_errno, const char *path, + struct iatt *buf, dict_t *xdata) +{ + mnt3_resolve_t *mres = NULL; + int ret = -EFAULT; + char *real_loc = NULL; + size_t path_len = 0; + size_t parent_path_len = 0; + char *parent_path = NULL; + char *absolute_path = NULL; + char *relative_path = NULL; + int mntstat = 0; + + GF_ASSERT (frame); + + mres = frame->local; + if (!mres || !path || (path[0] == '/') || (op_ret < 0)) + goto mnterr; + /* Finding current location of symlink */ + parent_path_len = strlen (mres->resolveloc.path) - strlen (mres->resolveloc.name); + parent_path = gf_strndup (mres->resolveloc.path, parent_path_len); + if (!parent_path) { + ret = -ENOMEM; + goto mnterr; + } + + relative_path = gf_strdup (path); + if (!relative_path) { + ret = -ENOMEM; + goto mnterr; + } + /* Resolving into absolute path */ + ret = gf_build_absolute_path (parent_path, relative_path, &absolute_path); + if (ret < 0) { + gf_log (GF_MNT, GF_LOG_ERROR, "Cannot resolve symlink, path" + "is out of boundary from current location %s" + "and with relative path %s pointed by symlink", + parent_path, relative_path); + + goto mnterr; + } + + /* Building the actual mount path to be mounted */ + path_len = strlen (mres->exp->vol->name) + strlen (absolute_path) + + strlen (mres->remainingdir) + 1; + real_loc = GF_CALLOC (1, path_len, gf_nfs_mt_char); + if (!real_loc) { + ret = -ENOMEM; + goto mnterr; + } + sprintf (real_loc , "%s%s", mres->exp->vol->name, absolute_path); + gf_path_strip_trailing_slashes (real_loc); + + /* There may entries after symlink in the mount path, + * we should include remaining entries too */ + if (strlen (mres->remainingdir) > 0) + strcat (real_loc, mres->remainingdir); + + gf_log (GF_MNT, GF_LOG_DEBUG, "Resolved path is : %s%s " + "and actual mount path is %s", + absolute_path, mres->remainingdir, real_loc); + + /* After the resolving the symlink , parsing should be done + * for the populated mount path + */ + ret = mnt3_parse_dir_exports (mres->req, mres->mstate, real_loc); + + if (ret) { + gf_log (GF_MNT, GF_LOG_ERROR, + "Resolved into an unknown path %s%s " + "from the current location of symlink %s", + absolute_path, mres->remainingdir, parent_path); + } + + GF_FREE (real_loc); + GF_FREE (absolute_path); + GF_FREE (parent_path); + GF_FREE (relative_path); + + return ret; + +mnterr: + mntstat = mnt3svc_errno_to_mnterr (-ret); + mnt3svc_mnt_error_reply (mres->req, mntstat); + if (absolute_path) + GF_FREE (absolute_path); + if (parent_path) + GF_FREE (parent_path); + if (relative_path) + GF_FREE (relative_path); + return ret; +} /* We will always have to perform a hard lookup on all the components of a * directory export for a mount request because in the mount reply we need the @@ -996,6 +1129,13 @@ __mnt3_resolve_subdir (mnt3_resolve_t *mres) } nfs_request_user_init (&nfu, mres->req); + if (IA_ISLNK (mres->resolveloc.inode->ia_type)) { + ret = nfs_readlink (mres->mstate->nfsx, mres->exp->vol, &nfu, + &mres->resolveloc, mnt3_readlink_cbk, mres); + gf_log (GF_MNT, GF_LOG_DEBUG, "Symlink found , need to resolve " + "into directory handle"); + goto err; + } ret = nfs_lookup (mres->mstate->nfsx, mres->exp->vol, &nfu, &mres->resolveloc, mnt3_resolve_subdir_cbk, mres); @@ -2054,13 +2194,15 @@ __mnt3udp_get_export_subdir_inode (struct svc_req *req, char *subdir, * TODO: Instead of linking against libgfapi.so, just for one API * i.e. glfs_resolve_at(), It would be cleaner if PATH name to * inode resolution code can be moved to libglusterfs.so or so. + * refer bugzilla for more details : + * https://bugzilla.redhat.com/show_bug.cgi?id=1161573 */ fs = glfs_new_from_ctx (exp->vol->ctx); if (!fs) return NULL; ret = glfs_resolve_at (fs, exp->vol, NULL, subdir, - &loc, &buf, 0 /* Follow link */, + &loc, &buf, 1 /* Follow link */, 0 /* Hard lookup */); glfs_free_from_ctx (fs); -- cgit