Commit 3f1bf940 authored by Ramon Nou's avatar Ramon Nou
Browse files

feat(client): support relative symlinks and directory target symlinks

- Resolve relative symlink targets against the symlink's parent directory per POSIX
- Relax the restriction in gkfs_mk_symlink prohibiting directory targets (fixes python venv lib64 -> lib)
parent 2876eec2
Loading
Loading
Loading
Loading
+28 −20
Changes for src/client/gkfs_libc.cpp: 28 added lines, 20 removed lines.
Original line number Diff line number Diff line
@@ -2266,22 +2266,26 @@ int
symlink(const char* path1, const char* path2) throw() {
    gkfs_init_routine_placeholder();
    if(GKFS_INTERCEPTION_ENABLED()) {
        std::string resolved;
        if(resolve_gkfs_path(AT_FDCWD, path1, resolved) ==
           PathStatus::Internal) {
            DEBUG_INFO("[GKFS] path 1 internal {}", resolved);
        std::string resolved2;
            if(resolve_gkfs_path(AT_FDCWD, path2, resolved2) ==
               PathStatus::Internal) {
        if(resolve_gkfs_path(AT_FDCWD, path2, resolved2) == PathStatus::Internal) {
            DEBUG_INFO("[GKFS] path 2 internal {}", resolved2);
                if(gkfs::config::metadata::symlink_support) {
                    // In Gekko we invert the parameters.
                    return gkfs::syscall::gkfs_mk_symlink(resolved2, resolved);
                } else {
            if(!gkfs::config::metadata::symlink_support) {
                DEBUG_INFO("[GKFS] symlinks not supported/compiled");
                errno = ENOTSUP;
                return -1;
            }
            std::string resolved;
            if(path1 && path1[0] != '/') {
                std::string parent_dir = gkfs::path::dirname(resolved2);
                std::string abs_target = gkfs::path::prepend_path(parent_dir, path1);
                if(abs_target.empty() || abs_target.front() != '/') {
                    resolved = "/" + abs_target;
                } else {
                    resolved = abs_target;
                }
                return gkfs::syscall::gkfs_mk_symlink(resolved2, resolved);
            } else if(resolve_gkfs_path(AT_FDCWD, path1, resolved) == PathStatus::Internal) {
                return gkfs::syscall::gkfs_mk_symlink(resolved2, resolved);
            }
        }
    }
@@ -2293,22 +2297,26 @@ int
symlinkat(const char* path1, int newdirfd, const char* path2) throw() {
    gkfs_init_routine_placeholder();
    if(GKFS_INTERCEPTION_ENABLED()) {
        std::string resolved;
        if(resolve_gkfs_path(AT_FDCWD, path1, resolved) ==
           PathStatus::Internal) {
            DEBUG_INFO("[GKFS] path 1 internal {}", resolved);
        std::string resolved2;
            if(resolve_gkfs_path(newdirfd, path2, resolved2) ==
               PathStatus::Internal) {
        if(resolve_gkfs_path(newdirfd, path2, resolved2) == PathStatus::Internal) {
            DEBUG_INFO("[GKFS] path 2 internal {}", resolved2);
                if(gkfs::config::metadata::symlink_support) {
                    // In Gekko we invert the parameters.
                    return gkfs::syscall::gkfs_mk_symlink(resolved2, resolved);
                } else {
            if(!gkfs::config::metadata::symlink_support) {
                DEBUG_INFO("[GKFS] symlinks not supported/compiled");
                errno = ENOTSUP;
                return -1;
            }
            std::string resolved;
            if(path1 && path1[0] != '/') {
                std::string parent_dir = gkfs::path::dirname(resolved2);
                std::string abs_target = gkfs::path::prepend_path(parent_dir, path1);
                if(abs_target.empty() || abs_target.front() != '/') {
                    resolved = "/" + abs_target;
                } else {
                    resolved = abs_target;
                }
                return gkfs::syscall::gkfs_mk_symlink(resolved2, resolved);
            } else if(resolve_gkfs_path(AT_FDCWD, path1, resolved) == PathStatus::Internal) {
                return gkfs::syscall::gkfs_mk_symlink(resolved2, resolved);
            }
        }
    }
+4 −14
Changes for src/client/gkfs_metadata.cpp: 4 added lines, 14 removed lines.
Original line number Diff line number Diff line
@@ -1639,22 +1639,12 @@ gkfs_getdents64(unsigned int fd, struct linux_dirent64* dirp,
 */
int
gkfs_mk_symlink(const std::string& path, const std::string& target_path) {
    /* The following check is not POSIX compliant.
     * In POSIX the target is not checked at all.
     *  Here if the target is a directory we raise a NOTSUP error.
     *  So that application know we don't support link to directory.
    /* In standard POSIX symlink(2), the target is arbitrary text and is not verified
     * at symlink creation time. Allowing directory targets enables virtual environments
     * (e.g. lib64 -> lib) and standard software distributions.
     */
    auto target_md = gkfs::utils::get_metadata(target_path, false);
    // std::string new_path = target_path; // unused
    if(target_md) {
        auto trg_mode = target_md->mode();
        if(!(S_ISREG(trg_mode) || S_ISLNK(trg_mode))) {
            assert(S_ISDIR(trg_mode));
            LOG(DEBUG, "Target path is a directory. Not supported");
            errno = ENOTSUP;
            return -1;
        }
    }
    (void) target_md;

    // cppcheck-suppress knownConditionTrueFalse
    if(check_parent_dir(path)) {
+16 −1
Changes for src/client/hooks.cpp: 16 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -633,7 +633,22 @@ hook_symlinkat(const char* oldname, int newdfd, const char* newname) {
                return -ENOTSUP;
            }
            std::string oldname_resolved;
            if(CTX->relativize_path(oldname, oldname_resolved)) {
            if(oldname[0] != gkfs::path::separator) {
                // oldname is a relative path (e.g. 'lib' or '../target').
                // POSIX resolves relative symlinks with respect to the parent
                // directory of the symlink (newname), NOT process CWD.
                std::string parent_dir = gkfs::path::dirname(newname_resolved);
                std::string abs_internal_target =
                        gkfs::path::prepend_path(parent_dir, oldname);
                if(abs_internal_target.empty() ||
                   abs_internal_target.front() != gkfs::path::separator) {
                    oldname_resolved = gkfs::path::separator + abs_internal_target;
                } else {
                    oldname_resolved = abs_internal_target;
                }
                return with_errno(gkfs::syscall::gkfs_mk_symlink(
                        newname_resolved, oldname_resolved));
            } else if(CTX->relativize_path(oldname, oldname_resolved)) {
                // Source is also in GekkoFS — create symlink inside GekkoFS.
                return with_errno(gkfs::syscall::gkfs_mk_symlink(
                        newname_resolved, oldname_resolved));