diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c6273f308eb17ed0fe4dd7abe0a983b7f28e6f5..86bd47ac62ceb915d51464f69e20b1d6c3b81a3e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,12 @@ set_property(CACHE RPC_PROTOCOL PROPERTY STRINGS message(STATUS "RPC protocol: '${RPC_PROTOCOL}'") add_definitions(-DRPC_PROTOCOL="${RPC_PROTOCOL}") +option(SYMLINK_SUPPORT "Compile with support for symlinks" ON) +if(SYMLINK_SUPPORT) + add_definitions(-DHAS_SYMLINKS) +endif() +message(STATUS "Symlink support: ${SYMLINK_SUPPORT}") + # Imported target add_library(RocksDB INTERFACE IMPORTED GLOBAL) target_link_libraries(RocksDB diff --git a/include/client/adafs_functions.hpp b/include/client/adafs_functions.hpp index c57b68942756de2d3d3d9fd637048fb58f678f09..58f641d13bd36816ceb859e65d32252c11329ab2 100644 --- a/include/client/adafs_functions.hpp +++ b/include/client/adafs_functions.hpp @@ -4,7 +4,7 @@ #include #include -std::shared_ptr adafs_metadata(const std::string& path); +std::shared_ptr adafs_metadata(const std::string& path, bool follow_links = false); int adafs_open(const std::string& path, mode_t mode, int flags); @@ -12,11 +12,9 @@ int adafs_mk_node(const std::string& path, mode_t mode); int adafs_rm_node(const std::string& path); -int adafs_access(const std::string& path, int mask); +int adafs_access(const std::string& path, int mask, bool follow_links = true); -int adafs_stat(const std::string& path, struct stat* buf); - -int adafs_stat64(const std::string& path, struct stat64* buf); +int adafs_stat(const std::string& path, struct stat* buf, bool follow_links = true); int adafs_statvfs(struct statvfs* buf); @@ -34,6 +32,11 @@ int adafs_dup(int oldfd); int adafs_dup2(int oldfd, int newfd); +#ifdef HAS_SYMLINKS +int adafs_mk_symlink(const std::string& path, const std::string& target_path); +int adafs_readlink(const std::string& path, char *buf, int bufsize); +#endif + ssize_t adafs_pwrite(std::shared_ptr file, const char * buf, size_t count, off64_t offset); diff --git a/include/client/intcp_functions.hpp b/include/client/intcp_functions.hpp index d3d2a422d810177e092c888272e91a3432778ea7..942566bbec4b291b4ebdb32781d1107d5eef926d 100644 --- a/include/client/intcp_functions.hpp +++ b/include/client/intcp_functions.hpp @@ -47,6 +47,15 @@ strong_alias(intcp_openat, __openat_2) int intcp_openat64(int dirfd, const char *path, int flags, ...); strong_alias(intcp_openat64, openat64) strong_alias(intcp_openat64, __openat64_2) +int intcp_symlink(const char* oldname, const char* newname) noexcept; +strong_alias(intcp_symlink, symlink) +strong_alias(intcp_symlink, __symlink) +int intcp_symlinkat(const char* oldname, int newfd, const char* newname) noexcept; +strong_alias(intcp_symlinkat, symlinkat) +ssize_t intcp_readlink(const char * cpath, char * buf, size_t bufsize) noexcept; +strong_alias(intcp_readlink, readlink) +ssize_t intcp_readlinkat(int dirfd, const char * cpath, char * buf, size_t bufsize) noexcept; +strong_alias(intcp_readlinkat, readlinkat) int intcp_statvfs(const char *path, struct statvfs *buf) noexcept; strong_alias(intcp_statvfs, statvfs) diff --git a/include/client/passthrough.hpp b/include/client/passthrough.hpp index d74fdd5ec2119d224f423f376cedee3d867479db..9014318591f4a7087147e86a81b9003068cb6d94 100644 --- a/include/client/passthrough.hpp +++ b/include/client/passthrough.hpp @@ -113,9 +113,10 @@ extern void* libc_get_current_dir_name; extern void* libc_link; extern void* libc_linkat; -extern void* libc_symlink; extern void* libc_symlinkat; +extern void* libc_readlinkat; + extern void* libc_realpath; void init_passthrough_if_needed(); diff --git a/include/client/preload_util.hpp b/include/client/preload_util.hpp index 6447c2dbe7a122c6b3b8a3b5267d83bca7e079f1..7da6043d9568a7f97fd1873d234ced089ec24057 100644 --- a/include/client/preload_util.hpp +++ b/include/client/preload_util.hpp @@ -43,6 +43,11 @@ extern hg_id_t rpc_trunc_data_id; extern hg_id_t rpc_get_dirents_id; extern hg_id_t rpc_chunk_stat_id; +#ifdef HAS_SYMLINKS +extern hg_id_t ipc_mk_symlink_id; +extern hg_id_t rpc_mk_symlink_id; +#endif + // function definitions int metadata_to_stat(const std::string& path, const Metadata& md, struct stat& attr); diff --git a/include/client/rpc/ld_rpc_metadentry.hpp b/include/client/rpc/ld_rpc_metadentry.hpp index 29fffb4f70c9e5e2539566d60f695ba2ce2e8f2d..d361ccafd02f3eef5e965cb79f59a5ba0081fe9a 100644 --- a/include/client/rpc/ld_rpc_metadentry.hpp +++ b/include/client/rpc/ld_rpc_metadentry.hpp @@ -29,6 +29,10 @@ int get_metadentry_size(const std::string& path, off64_t& ret_size); void get_dirents(OpenDir& open_dir); +#ifdef HAS_SYMLINKS +int mk_symlink(const std::string& path, const std::string& target_path); +#endif + } // end namespace rpc_send diff --git a/include/daemon/handler/rpc_defs.hpp b/include/daemon/handler/rpc_defs.hpp index b334dfa4f6970efb04767ebcf7fd46aee2a13425..daf63b7c96bfc823372e020136e864c1e6e386eb 100644 --- a/include/daemon/handler/rpc_defs.hpp +++ b/include/daemon/handler/rpc_defs.hpp @@ -26,6 +26,11 @@ DECLARE_MARGO_RPC_HANDLER(rpc_srv_update_metadentry_size) DECLARE_MARGO_RPC_HANDLER(rpc_srv_get_dirents) +#ifdef HAS_SYMLINKS +DECLARE_MARGO_RPC_HANDLER(rpc_srv_mk_symlink) +#endif + + // data DECLARE_MARGO_RPC_HANDLER(rpc_srv_read_data) diff --git a/include/global/global_defs.hpp b/include/global/global_defs.hpp index e11da8cdd51f0d8b11b79d16340e73cf4b620901..5adc1c16b87ec698647a91104b49d3147a17d66b 100644 --- a/include/global/global_defs.hpp +++ b/include/global/global_defs.hpp @@ -14,6 +14,9 @@ namespace hg_tag { constexpr auto get_metadentry_size = "rpc_srv_get_metadentry_size"; constexpr auto update_metadentry_size = "rpc_srv_update_metadentry_size"; constexpr auto get_dirents = "rpc_srv_get_dirents"; +#ifdef HAS_SYMLINKS + constexpr auto mk_symlink = "rpc_srv_mk_symlink"; +#endif constexpr auto write_data = "rpc_srv_write_data"; constexpr auto read_data = "rpc_srv_read_data"; constexpr auto trunc_data = "rpc_srv_trunc_data"; diff --git a/include/global/metadata.hpp b/include/global/metadata.hpp index b1388ea6811ed18c26e2a6d8a3f7c8b1b84b1b54..62209009a977eebcd56c6b5907d233f766387f1e 100644 --- a/include/global/metadata.hpp +++ b/include/global/metadata.hpp @@ -5,9 +5,13 @@ #include "global/configure.hpp" #include +#include #include +constexpr mode_t LINK_MODE = ((S_IRWXU | S_IRWXG | S_IRWXO) | S_IFLNK); + + class Metadata { private: time_t atime_; // access time. gets updated on file access unless mounted with noatime @@ -17,10 +21,17 @@ private: nlink_t link_count_; // number of names for this inode (hardlinks) size_t size_; // size_ in bytes, might be computed instead of stored blkcnt_t blocks_; // allocated file system blocks_ +#ifdef HAS_SYMLINKS + std::string target_path_; // For links this is the path of the target file +#endif + public: Metadata(); explicit Metadata(mode_t mode); +#ifdef HAS_SYMLINKS + Metadata(mode_t mode, const std::string& target_path); +#endif // Construct from a binary representation of the object explicit Metadata(const std::string& binary_str); @@ -44,6 +55,11 @@ public: void size(size_t size_); blkcnt_t blocks() const; void blocks(blkcnt_t blocks_); +#ifdef HAS_SYMLINKS + std::string target_path() const; + void target_path(const std::string& target_path); + bool is_link() const; +#endif }; diff --git a/include/global/rpc/rpc_types.hpp b/include/global/rpc/rpc_types.hpp index 9e0f5a1891ba0985cc9df8a70a3fb57cde7799ae..066bd7fec3a939812277741e08ca2bb41cdac584 100644 --- a/include/global/rpc/rpc_types.hpp +++ b/include/global/rpc/rpc_types.hpp @@ -58,6 +58,13 @@ MERCURY_GEN_PROC(rpc_update_metadentry_size_out_t, ((hg_int32_t) (err)) MERCURY_GEN_PROC(rpc_get_metadentry_size_out_t, ((hg_int32_t) (err)) ((hg_int64_t) (ret_size))) +#ifdef HAS_SYMLINKS +MERCURY_GEN_PROC(rpc_mk_symlink_in_t, + ((hg_const_string_t) (path))\ + ((hg_const_string_t) (target_path)) +) +#endif + // data MERCURY_GEN_PROC(rpc_read_data_in_t, ((hg_const_string_t) (path))\ diff --git a/src/client/adafs_functions.cpp b/src/client/adafs_functions.cpp index 352a0cf768eb75ea7068b597add5dfe4b40a9a1a..dee0271d8b99b6bb47370db5b8dc2bf54e6267de 100644 --- a/src/client/adafs_functions.cpp +++ b/src/client/adafs_functions.cpp @@ -68,6 +68,17 @@ int adafs_open(const std::string& path, mode_t mode, int flags) { return -1; } +#ifdef HAS_SYMLINKS + if (md->is_link()) { + if (flags & O_NOFOLLOW) { + CTX->log()->warn("{}() symlink found and O_NOFOLLOW flag was specified", __func__); + errno = ELOOP; + return -1; + } + return adafs_open(md->target_path(), mode, flags); + } +#endif + if(S_ISDIR(md->mode())) { return adafs_opendir(path); } @@ -87,13 +98,29 @@ int adafs_open(const std::string& path, mode_t mode, int flags) { return CTX->file_map()->add(std::make_shared(path, flags)); } -int adafs_mk_node(const std::string& path, const mode_t mode) { +int adafs_mk_node(const std::string& path, mode_t mode) { init_ld_env_if_needed(); //file type must be set - assert((mode & S_IFMT) != 0); - //file type must be either regular file or directory - assert(S_ISREG(mode) || S_ISDIR(mode)); + switch (mode & S_IFMT) { + case 0: + mode |= S_IFREG; + break; + case S_IFREG: // intentionally fall-through + case S_IFDIR: + break; + case S_IFCHR: // intentionally fall-through + case S_IFBLK: + case S_IFIFO: + case S_IFSOCK: + CTX->log()->warn("{}() unsupported node type", __func__); + errno = ENOTSUP; + return -1; + default: + CTX->log()->warn("{}() unrecognized node type", __func__); + errno = EINVAL; + return -1; + } auto p_comp = dirname(path); auto md = adafs_metadata(p_comp); @@ -125,9 +152,9 @@ int adafs_rm_node(const std::string& path) { return rpc_send::rm_node(path, !has_data); } -int adafs_access(const std::string& path, const int mask) { +int adafs_access(const std::string& path, const int mask, bool follow_links) { init_ld_env_if_needed(); - auto md = adafs_metadata(path); + auto md = adafs_metadata(path, follow_links); if (!md) { errno = ENOENT; return -1; @@ -135,9 +162,9 @@ int adafs_access(const std::string& path, const int mask) { return 0; } -int adafs_stat(const string& path, struct stat* buf) { +int adafs_stat(const string& path, struct stat* buf, bool follow_links) { init_ld_env_if_needed(); - auto md = adafs_metadata(path); + auto md = adafs_metadata(path, follow_links); if (!md) { return -1; } @@ -145,12 +172,24 @@ int adafs_stat(const string& path, struct stat* buf) { return 0; } -std::shared_ptr adafs_metadata(const string& path) { +std::shared_ptr adafs_metadata(const string& path, bool follow_links) { std::string attr; auto err = rpc_send::stat(path, attr); if (err) { return nullptr; } +#ifdef HAS_SYMLINKS + if (follow_links) { + Metadata md{attr}; + while (md.is_link()) { + err = rpc_send::stat(md.target_path(), attr); + if (err) { + return nullptr; + } + md = Metadata{attr}; + } + } +#endif return make_shared(attr); } @@ -266,11 +305,10 @@ int adafs_truncate(const std::string& path, off_t length) { return -1; } - auto md = adafs_metadata(path); + auto md = adafs_metadata(path, true); if (!md) { return -1; } - auto size = md->size(); if(static_cast(length) > size) { CTX->log()->debug("{}() length is greater then file size: {} > {}", @@ -479,4 +517,73 @@ struct dirent * adafs_readdir(int fd){ return nullptr; } return open_dir->readdir(); -} \ No newline at end of file +} + +#ifdef HAS_SYMLINKS + +int adafs_mk_symlink(const std::string& path, const std::string& target_path) { + init_ld_env_if_needed(); + /* 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. + */ + auto target_md = adafs_metadata(target_path, false); + if (target_md != nullptr) { + auto trg_mode = target_md->mode(); + if (!(S_ISREG(trg_mode) || S_ISLNK(trg_mode))) { + assert(S_ISDIR(trg_mode)); + CTX->log()->debug("{}() target path is a directory. Not supported", __func__); + errno = ENOTSUP; + return -1; + } + } + + auto p_comp = dirname(path); + auto md = adafs_metadata(p_comp, false); + if (md == nullptr) { + CTX->log()->debug("{}() parent component does not exist: '{}'", __func__, p_comp); + errno = ENOENT; + return -1; + } + if (!S_ISDIR(md->mode())) { + CTX->log()->debug("{}() parent component is not a directory: '{}'", __func__, p_comp); + errno = ENOTDIR; + return -1; + } + + auto link_md = adafs_metadata(path, false); + if (link_md != nullptr) { + CTX->log()->debug("{}() Link exists: '{}'", __func__, p_comp); + errno = EEXIST; + return -1; + } + + return rpc_send::mk_symlink(path, target_path); +} + +int adafs_readlink(const std::string& path, char *buf, int bufsize) { + init_ld_env_if_needed(); + auto md = adafs_metadata(path, false); + if (md == nullptr) { + CTX->log()->debug("{}() named link doesn't exists", __func__); + return -1; + } + if (!(md->is_link())) { + CTX->log()->debug("{}() The named file is not a symbolic link", __func__); + errno = EINVAL; + return -1; + } + int path_size = md->target_path().size() + CTX->mountdir().size(); + if (path_size >= bufsize) { + CTX->log()->warn("{}() destination buffer size is to short: {} < {}, {} ", __func__, bufsize, path_size, md->target_path()); + errno = ENAMETOOLONG; + return -1; + } + + CTX->mountdir().copy(buf, CTX->mountdir().size()); + std::strcpy(buf + CTX->mountdir().size(), md->target_path().c_str()); + return path_size; +} + +#endif diff --git a/src/client/intcp_functions.cpp b/src/client/intcp_functions.cpp index 5f3ee0877e78337f856b666005507982f33f48bf..74b133a236487cf88f0301f765ea7d97ac871788 100644 --- a/src/client/intcp_functions.cpp +++ b/src/client/intcp_functions.cpp @@ -641,8 +641,8 @@ int faccessat(int dirfd, const char* cpath, int mode, int flags) noexcept { //TODO handle the AT_EACCESS flag std::string resolved; - auto rstatus = CTX->relativize_fd_path(dirfd, cpath, resolved, - !(flags & AT_SYMLINK_NOFOLLOW)); + bool follow_links = !(flags & AT_SYMLINK_NOFOLLOW); + auto rstatus = CTX->relativize_fd_path(dirfd, cpath, resolved, follow_links); switch(rstatus) { case RelativizeStatus::fd_unknown: return LIBC_FUNC(faccessat, dirfd, cpath, mode, flags); @@ -655,7 +655,7 @@ int faccessat(int dirfd, const char* cpath, int mode, int flags) noexcept { return -1; case RelativizeStatus::internal: - return adafs_access(resolved, mode); + return adafs_access(resolved, mode, follow_links); default: CTX->log()->error("{}() relativize status unknown: {}", __func__); @@ -700,8 +700,7 @@ int lstat(const char* path, struct stat* buf) noexcept { if (!CTX->relativize_path(path, rel_path, false)) { return LIBC_FUNC(lstat, rel_path.c_str(), buf); } - CTX->log()->warn("{}() No symlinks are supported. Stats will always target the given path", __func__); - return adafs_stat(rel_path, buf); + return adafs_stat(rel_path, buf, false); } int __xstat(int ver, const char* path, struct stat* buf) noexcept { @@ -760,8 +759,8 @@ int __fxstatat(int ver, int dirfd, const char* cpath, struct stat* buf, int flag } std::string resolved; - auto rstatus = CTX->relativize_fd_path(dirfd, cpath, resolved, - !(flags & AT_SYMLINK_NOFOLLOW)); + bool follow_links = !(flags & AT_SYMLINK_NOFOLLOW); + auto rstatus = CTX->relativize_fd_path(dirfd, cpath, resolved, follow_links); switch(rstatus) { case RelativizeStatus::fd_unknown: return LIBC_FUNC(__fxstatat, ver, dirfd, cpath, buf, flags); @@ -774,7 +773,7 @@ int __fxstatat(int ver, int dirfd, const char* cpath, struct stat* buf, int flag return -1; case RelativizeStatus::internal: - return adafs_stat(resolved, buf); + return adafs_stat(resolved, buf, follow_links); default: CTX->log()->error("{}() relativize status unknown: {}", __func__); @@ -813,9 +812,9 @@ int __lxstat(int ver, const char* path, struct stat* buf) noexcept { CTX->log()->trace("{}() called with path '{}'", __func__, path); std::string rel_path; if (!CTX->relativize_path(path, rel_path, false)) { - return LIBC_FUNC(__lxstat, ver, path, buf); + return LIBC_FUNC(__lxstat, ver, rel_path.c_str(), buf); } - return adafs_stat(rel_path, buf); + return adafs_stat(rel_path, buf, false); } int __lxstat64(int ver, const char* path, struct stat64* buf) noexcept { @@ -1406,38 +1405,114 @@ int linkat(int olddirfd, const char *oldpath, return LIBC_FUNC(linkat, olddirfd, oldpath, newdirfd, newpath, flags); } -int symlink(const char* oldpath, const char* newpath) noexcept { +int intcp_symlink(const char* oldname, const char* newname) noexcept { + return symlinkat(oldname, AT_FDCWD, newname); +} + +int intcp_symlinkat(const char* oldname, int newdfd, const char* newname) noexcept { init_passthrough_if_needed(); if(!CTX->interception_enabled()) { - return LIBC_FUNC(symlink, oldpath, newpath); + return LIBC_FUNC(symlinkat, oldname, newdfd, newname); } - CTX->log()->trace("{}() called [oldpath: '{}', newpath: '{}']", - __func__, oldpath, newpath); - std::string rel_oldpath; - bool oldpath_internal = CTX->relativize_path(oldpath, rel_oldpath); + CTX->log()->trace("{}() called with oldname '{}', new fd {}, new name '{}'", + __func__, oldname, newdfd, newname); - std::string rel_newpath; - bool newpath_internal = CTX->relativize_path(newpath, rel_newpath); + std::string oldname_resolved; + auto oldname_is_internal = CTX->relativize_path(oldname, oldname_resolved); - if (oldpath_internal || newpath_internal) { - CTX->log()->error("{}() not implemented", __func__); +#ifndef HAS_SYMLINKS + if (oldname_is_internal) { + CTX->log()->warn("{}() attempt to create link to GekkoFS namespace: operation not supported." + "Enable through compile flags `-DHAS_SYMLINKS` or" + "with the cmake option `SYMLINK_SUPPORT`.", __func__); errno = ENOTSUP; return -1; } - return LIBC_FUNC(symlink, rel_oldpath.c_str(), rel_newpath.c_str()); +#endif + + std::string newname_resolved; + auto new_status = CTX->relativize_fd_path(newdfd, newname, newname_resolved, false); + switch(new_status) { + case RelativizeStatus::fd_unknown: + return LIBC_FUNC(symlinkat, oldname, newdfd, newname); + + case RelativizeStatus::external: + if(oldname_is_internal) { + CTX->log()->warn("{}() attempt to create link from outside to GekkoFS namespace: operation not supported." + "Enable through compile flags `-DHAS_SYMLINKS` or" + "with the cmake option `SYMLINK_SUPPORT`.", __func__); + errno = ENOTSUP; + return -1; + } + return LIBC_FUNC(symlinkat, oldname, newdfd, newname_resolved.c_str()); + + case RelativizeStatus::fd_not_a_dir: + errno = ENOTDIR; + return -1; + + case RelativizeStatus::internal: +#ifdef HAS_SYMLINKS + if(!oldname_is_internal) { + CTX->log()->warn("{}() attempt to create link from inside GekkoFS to outside: operation not supported", __func__); + errno = ENOTSUP; + return -1; + } + return adafs_mk_symlink(newname_resolved, oldname_resolved); +#else + CTX->log()->warn("{}() attempt to create link from GekkoFS namespace: operation not supported." + "Enable through compile flags `-DHAS_SYMLINKS` or" + "with the cmake option `SYMLINK_SUPPORT`.", __func__); + errno = ENOTSUP; + return -1; +#endif + + default: + CTX->log()->error("{}() relativize status unknown", __func__); + errno = EINVAL; + return -1; + } } -int symlinkat(const char* oldpath, int fd, const char* newpath) noexcept { +ssize_t intcp_readlink(const char * cpath, char * buf, size_t bufsize) noexcept { + return intcp_readlinkat(AT_FDCWD, cpath, buf, bufsize); +} + +ssize_t intcp_readlinkat(int dirfd, const char * cpath, char * buf, size_t bufsize) noexcept { init_passthrough_if_needed(); - if(CTX->interception_enabled()) { - CTX->log()->trace("{}() called [oldpath: '{}', newpath: '{}']", - __func__, oldpath, newpath); - CTX->log()->error("{}() not implemented", __func__); - errno = ENOTSUP; - return -1; + if(!CTX->interception_enabled()) { + return LIBC_FUNC(readlinkat, dirfd, cpath, buf, bufsize); + } + + CTX->log()->trace("{}() called with path '{}' dirfd {}, bufsize {}", + __func__, cpath, dirfd, bufsize); + + std::string resolved; + auto rstatus = CTX->relativize_fd_path(dirfd, cpath, resolved, false); + switch(rstatus) { + case RelativizeStatus::fd_unknown: + return LIBC_FUNC(readlinkat, dirfd, cpath, buf, bufsize); + + case RelativizeStatus::external: + return LIBC_FUNC(readlinkat, dirfd, cpath, buf, bufsize); + + case RelativizeStatus::fd_not_a_dir: + errno = ENOTDIR; + return -1; + + case RelativizeStatus::internal: +#ifdef HAS_SYMLINKS + return adafs_readlink(resolved, buf, bufsize); +#else + CTX->log()->warn("{}() symlink not supported", __func__); + errno = EINVAL; + return -1; +#endif + default: + CTX->log()->error("{}() relativize status unknown: {}", __func__); + errno = EINVAL; + return -1; } - return LIBC_FUNC(symlinkat, oldpath, fd, newpath); } char *realpath(const char *path, char *resolved_path) { diff --git a/src/client/passthrough.cpp b/src/client/passthrough.cpp index e8833ba328256b36e87c3735a9769fdef799a6d9..33c7361bc0d32a85c30f8ea6f3e82fa70796d701 100644 --- a/src/client/passthrough.cpp +++ b/src/client/passthrough.cpp @@ -114,9 +114,10 @@ void* libc_get_current_dir_name; void* libc_link; void* libc_linkat; -void* libc_symlink; void* libc_symlinkat; +void* libc_readlinkat; + void* libc_realpath; @@ -229,9 +230,10 @@ void init_passthrough_() { libc_link = dlsym(libc, "link"); libc_linkat = dlsym(libc, "linkat"); - libc_symlink = dlsym(libc, "symlink"); libc_symlinkat = dlsym(libc, "symlinkat"); + libc_readlinkat = dlsym(libc, "readlinkat"); + libc_realpath = dlsym(libc, "realpath"); } diff --git a/src/client/preload.cpp b/src/client/preload.cpp index 20c2bb90ebc5b1a13e603ba8af293dffec831798..6c5adee63a677a12c309a431e627237846baa764 100644 --- a/src/client/preload.cpp +++ b/src/client/preload.cpp @@ -24,6 +24,7 @@ hg_id_t rpc_decr_size_id; hg_id_t rpc_update_metadentry_id; hg_id_t rpc_get_metadentry_size_id; hg_id_t rpc_update_metadentry_size_id; +hg_id_t rpc_mk_symlink_id; hg_id_t rpc_write_data_id; hg_id_t rpc_read_data_id; hg_id_t rpc_trunc_data_id; @@ -61,6 +62,15 @@ void register_client_rpcs(margo_instance_id mid) { rpc_update_metadentry_size_in_t, rpc_update_metadentry_size_out_t, NULL); + +#ifdef HAS_SYMLINKS + rpc_mk_symlink_id = MARGO_REGISTER(mid, + hg_tag::mk_symlink, + rpc_mk_symlink_in_t, + rpc_err_out_t, + NULL); +#endif + rpc_write_data_id = MARGO_REGISTER(mid, hg_tag::write_data, rpc_write_data_in_t, rpc_data_out_t, NULL); rpc_read_data_id = MARGO_REGISTER(mid, hg_tag::read_data, rpc_read_data_in_t, rpc_data_out_t, diff --git a/src/client/preload_util.cpp b/src/client/preload_util.cpp index 759495d0200abb1fe53ba789054873b1be92522d..a292dadfcc9839bf080b078aa18e60f8eba5e764 100644 --- a/src/client/preload_util.cpp +++ b/src/client/preload_util.cpp @@ -38,6 +38,12 @@ int metadata_to_stat(const std::string& path, const Metadata& md, struct stat& a memset(&attr.st_ctim, 0, sizeof(timespec)); attr.st_mode = md.mode(); + +#ifdef HAS_SYMLINKS + if (md.is_link()) + attr.st_size = md.target_path().size() + CTX->mountdir().size(); + else +#endif attr.st_size = md.size(); if (CTX->fs_conf()->atime_state) { diff --git a/src/client/rpc/ld_rpc_metadentry.cpp b/src/client/rpc/ld_rpc_metadentry.cpp index 64d0ee1e5dcb395c3dae94e46333fa3f6e9de192..215ce2083180e4f9933d332a7b0b0b7322c57f02 100644 --- a/src/client/rpc/ld_rpc_metadentry.cpp +++ b/src/client/rpc/ld_rpc_metadentry.cpp @@ -453,4 +453,48 @@ void get_dirents(OpenDir& open_dir){ } } +#ifdef HAS_SYMLINKS + +int mk_symlink(const std::string& path, const std::string& target_path) { + hg_handle_t handle; + rpc_mk_symlink_in_t in{}; + rpc_err_out_t out{}; + int err = EUNKNOWN; + // fill in + in.path = path.c_str(); + in.target_path = target_path.c_str(); + // Create handle + CTX->log()->debug("{}() Creating Mercury handle ...", __func__); + auto ret = margo_create_wrap(rpc_mk_symlink_id, path, handle); + if (ret != HG_SUCCESS) { + errno = EBUSY; + return -1; + } + // Send rpc + CTX->log()->debug("{}() About to send RPC ...", __func__); + ret = margo_forward_timed_wrap(handle, &in); + // Get response + if (ret == HG_SUCCESS) { + CTX->log()->trace("{}() Waiting for response", __func__); + ret = margo_get_output(handle, &out); + if (ret == HG_SUCCESS) { + CTX->log()->debug("{}() Got response success: {}", __func__, out.err); + err = out.err; + } else { + // something is wrong + errno = EBUSY; + CTX->log()->error("{}() while getting rpc output", __func__); + } + /* clean up resources consumed by this rpc */ + margo_free_output(handle, &out); + } else { + CTX->log()->warn("{}() timed out"); + errno = EBUSY; + } + margo_destroy(handle); + return err; +} + +#endif + } //end namespace rpc_send diff --git a/src/daemon/adafs_daemon.cpp b/src/daemon/adafs_daemon.cpp index 0f48c3f17a155ac942089bcece17ffd355f09f82..dc59ebbadedeed2778b427b264b6f2e0d83140fb 100644 --- a/src/daemon/adafs_daemon.cpp +++ b/src/daemon/adafs_daemon.cpp @@ -237,6 +237,9 @@ void register_server_rpcs(margo_instance_id mid) { rpc_update_metadentry_size_out_t, rpc_srv_update_metadentry_size); MARGO_REGISTER(mid, hg_tag::get_dirents, rpc_get_dirents_in_t, rpc_get_dirents_out_t, rpc_srv_get_dirents); +#ifdef HAS_SYMLINKS + MARGO_REGISTER(mid, hg_tag::mk_symlink, rpc_mk_symlink_in_t, rpc_err_out_t, rpc_srv_mk_symlink); +#endif MARGO_REGISTER(mid, hg_tag::write_data, rpc_write_data_in_t, rpc_data_out_t, rpc_srv_write_data); MARGO_REGISTER(mid, hg_tag::read_data, rpc_read_data_in_t, rpc_data_out_t, rpc_srv_read_data); MARGO_REGISTER(mid, hg_tag::trunc_data, rpc_trunc_in_t, rpc_err_out_t, rpc_srv_trunc_data); diff --git a/src/daemon/handler/h_metadentry.cpp b/src/daemon/handler/h_metadentry.cpp index dd3d0d14430e8dd4d1e7d9143a41db8d4262d815..4e62747a150a82ad0477a1a4cb21d0e263ac1a06 100644 --- a/src/daemon/handler/h_metadentry.cpp +++ b/src/daemon/handler/h_metadentry.cpp @@ -362,4 +362,42 @@ static hg_return_t rpc_srv_get_dirents(hg_handle_t handle) { return rpc_cleanup_respond(&handle, &in, &out, &bulk_handle); } -DEFINE_MARGO_RPC_HANDLER(rpc_srv_get_dirents) \ No newline at end of file +DEFINE_MARGO_RPC_HANDLER(rpc_srv_get_dirents) + +#ifdef HAS_SYMLINKS + +static hg_return_t rpc_srv_mk_symlink(hg_handle_t handle) { + rpc_mk_symlink_in_t in{}; + rpc_err_out_t out{}; + + auto ret = margo_get_input(handle, &in); + if (ret != HG_SUCCESS) { + ADAFS_DATA->spdlogger()->error("{}() Failed to retrieve input from handle", __func__); + } + ADAFS_DATA->spdlogger()->debug("{}() Got RPC (from local {}) with path {}", __func__, + (margo_get_info(handle)->context_id == ADAFS_DATA->host_id()), in.path); + + try { + Metadata md = {LINK_MODE, in.target_path}; + // create metadentry + create_metadentry(in.path, md); + out.err = 0; + } catch (const std::exception& e) { + ADAFS_DATA->spdlogger()->error("{}() Failed to create metadentry: {}", __func__, e.what()); + out.err = -1; + } + ADAFS_DATA->spdlogger()->debug("{}() Sending output err {}", __func__, out.err); + auto hret = margo_respond(handle, &out); + if (hret != HG_SUCCESS) { + ADAFS_DATA->spdlogger()->error("{}() Failed to respond"); + } + + // Destroy handle when finished + margo_free_input(handle, &in); + margo_destroy(handle); + return HG_SUCCESS; +} + +DEFINE_MARGO_RPC_HANDLER(rpc_srv_mk_symlink) + +#endif diff --git a/src/global/metadata.cpp b/src/global/metadata.cpp index 370f8628a7d369140d08e4ada5263e2a63003bb9..3d9a057a3f7757c4c14234627e5b49391872070d 100644 --- a/src/global/metadata.cpp +++ b/src/global/metadata.cpp @@ -19,7 +19,30 @@ Metadata::Metadata(const mode_t mode) : link_count_(0), size_(0), blocks_(0) -{} +{ + assert(S_ISDIR(mode_) || S_ISREG(mode_)); +} + +#ifdef HAS_SYMLINKS + +Metadata::Metadata(const mode_t mode, const std::string& target_path) : + atime_(), + mtime_(), + ctime_(), + mode_(mode), + link_count_(0), + size_(0), + blocks_(0), + target_path_(target_path) +{ + assert(S_ISLNK(mode_) || S_ISDIR(mode_) || S_ISREG(mode_)); + // target_path should be there only if this is a link + assert(target_path_.empty() || S_ISLNK(mode_)); + // target_path should be absolute + assert(target_path_.empty() || target_path_[0] == '/'); +} + +#endif Metadata::Metadata(const std::string& binary_str) { size_t read = 0; @@ -69,6 +92,16 @@ Metadata::Metadata(const std::string& binary_str) { assert(read > 0); ptr += read; } + +#ifdef HAS_SYMLINKS + // Read target_path + assert(*ptr == MSP); + target_path_ = ++ptr; + // target_path should be there only if this is a link + assert(target_path_.size() == 0 || S_ISLNK(mode_)); + ptr += target_path_.size(); +#endif + // we consumed all the binary string assert(*ptr == '\0'); } @@ -100,6 +133,12 @@ std::string Metadata::serialize() const s += MSP; s += fmt::format_int(blocks_).c_str(); } + +#ifdef HAS_SYMLINKS + s += MSP; + s += target_path_; +#endif + return s; } @@ -179,3 +218,24 @@ blkcnt_t Metadata::blocks() const { void Metadata::blocks(blkcnt_t blocks_) { Metadata::blocks_ = blocks_; } + +#ifdef HAS_SYMLINKS + +std::string Metadata::target_path() const { + assert(!target_path_.empty()); + return target_path_; +} + +void Metadata::target_path(const std::string& target_path) { + // target_path should be there only if this is a link + assert(target_path.empty() || S_ISLNK(mode_)); + // target_path should be absolute + assert(target_path.empty() || target_path[0] == '/'); + target_path_ = target_path; +} + +bool Metadata::is_link() const { + return S_ISLNK(mode_); +} + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c8a426e0f72175c6d456590ae410b108bdb25954..110cfe9fc32a671afb138618cdc07c9ca495c169 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -23,6 +23,8 @@ add_executable(gkfs_test_dir dir_test.cpp) add_executable(gkfs_test_truncate truncate.cpp) +add_executable(gkfs_test_symlink symlink_test.cpp) + add_executable(gkfs_test_path_resolution path_resolution.cpp) find_package(MPI) diff --git a/test/symlink_test.cpp b/test/symlink_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ad21893957024bcf5a04be4fcce01db2750998fd --- /dev/null +++ b/test/symlink_test.cpp @@ -0,0 +1,248 @@ +/* Test fs functionality involving links */ + +#include +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) { + + const std::string mountdir = "/tmp/mountdir"; + const std::string dir_int = mountdir + "/dir"; + const std::string dir_ext = "/tmp/dir"; + const std::string target_int = dir_int + "/target"; + const std::string target_ext = dir_ext + "/target"; + const std::string link_int = dir_int + "/link"; + const std::string link_ext = dir_ext + "/tmp/link"; + + char buffIn[] = "oops."; + char *buffOut = new char[strlen(buffIn) + 1]; + + struct stat st; + int ret; + int fd; + + // Clean external dir + ret = rmdir(dir_ext.c_str()); + if (ret != 0) { + if (errno != ENOENT) { + std::cerr << "ERROR: cannot remove internal dir: " << strerror(errno) << std::endl; + return -1; + } + } + ret = mkdir(dir_ext.c_str(), 0770); + if (ret != 0) { + std::cerr << "ERROR: cannot create external dir: " << strerror(errno) << std::endl; + return -1; + } + + + // Clean internal dir + ret = rmdir(dir_int.c_str()); + if (ret != 0) { + if (errno != ENOENT) { + std::cerr << "ERROR: cannot remove internal dir: " << strerror(errno) << std::endl; + return -1; + } + } + ret = mkdir(dir_int.c_str(), 0770); + if (ret != 0) { + std::cerr << "ERROR: cannot create internal dir: " << strerror(errno) << std::endl; + return -1; + } + + + // Create link to directory: NOT SUPPORTED + ret = symlink(dir_int.c_str(), link_int.c_str()); + if (ret != -1) { + std::cerr << "ERROR: Succeeded on creating link to directory" << std::endl; + return -1; + } + if(errno != ENOTSUP){ + std::cerr << "ERROR: wrong error number on link to directory: " << errno << std::endl; + return -1; + } + assert(lstat(link_int.c_str(), &st) != 0 && errno == ENOENT); + + + // Create link from inside to outside: NOT SUPPORTED + ret = symlink(target_ext.c_str(), link_int.c_str()); + if (ret != -1) { + std::cerr << "ERROR: Succeeded on creating link to outside" << std::endl; + return -1; + } + if(errno != ENOTSUP){ + std::cerr << "ERROR: wrong error number on link to outside: " << errno << std::endl; + return -1; + } + assert(lstat(link_int.c_str(), &st) != 0 && errno == ENOENT); + + + // Create link from outside to inside: NOT SUPPORTED + ret = symlink(target_int.c_str(), link_ext.c_str()); + if (ret != -1) { + std::cerr << "ERROR: Succeeded on creating link from outside" << std::endl; + return -1; + } + if(errno != ENOTSUP){ + std::cerr << "ERROR: wrong error number on link from outside: " << errno << std::endl; + return -1; + } + assert(lstat(link_ext.c_str(), &st) != 0 && errno == ENOENT); + + + // Create regular link + ret = symlink(target_int.c_str(), link_int.c_str()); + if (ret < 0) { + std::cerr << "ERROR: Failed to create link: " << strerror(errno) << std::endl; + return -1; + } + + // Check link stat + ret = lstat(link_int.c_str(), &st); + if (ret != 0) { + std::cerr << "ERROR: Failed to stat link:" << strerror(errno) << std::endl; + return -1; + } + // Check link mode + if (!S_ISLNK(st.st_mode)) { + std::cerr << "ERROR: Link has wrong file type" << std::endl; + return -1; + } + // Check link size + if (st.st_size != target_int.size()) { + std::cerr << "ERROR: Link has wrong size" << std::endl; + return -1; + } + + + // Check readlink + char* target_path = new char[target_int.size() + 1]; + ret = readlink(link_int.c_str(), target_path, target_int.size() + 1); + if (ret <= 0) { + std::cerr << "ERROR: Failed to retrieve link path: " << strerror(errno) << std::endl; + return -1; + } + // Check return value, should be the length of target path + if (ret != target_int.size()) { + std::cerr << "ERROR: readlink returned unexpected value: " << ret << std::endl; + return -1; + } + // Check returned string + if (std::string(target_path) != target_int) { + std::cerr << "ERROR: readlink returned unexpected target path: " << std::string(target_path) << std::endl; + return -1; + } + + // Overwrite link + fd = symlink(target_int.c_str(), link_int.c_str()); + if (fd == 0) { + std::cerr << "ERROR: Succeed on overwriting link" << std::endl; + return -1; + } + if(errno != EEXIST){ + std::cerr << "ERROR: wrong error number on overwriting symlink" << errno << std::endl; + return -1; + } + + // Check target stat + ret = stat(link_int.c_str(), &st); + if (ret != -1) { + std::cerr << "ERROR: Succeed on stating unexistent target through link" << std::endl; + return -1; + } + if(errno != ENOENT){ + std::cerr << "ERROR: wrong error number on stating unexistent target through link" << std::endl; + return -1; + } + + + /* Write on link */ + fd = open(link_int.c_str(), O_WRONLY | O_CREAT, 0770); + if(fd < 0){ + std::cerr << "ERROR: opening target for write" << strerror(errno) << std::endl; + return -1; + } + auto nw = write(fd, buffIn, strlen(buffIn)); + if(nw != strlen(buffIn)){ + std::cerr << "ERROR: writing target" << strerror(errno) << std::endl; + return -1; + } + if(close(fd) != 0){ + std::cerr << "ERROR: closing target" << strerror(errno) << std::endl; + return -1; + } + + + // Check target stat through link + ret = stat(link_int.c_str(), &st); + if (ret != 0) { + std::cerr << "ERROR: Failed to stat target through link: " << strerror(errno) << std::endl; + return -1; + } + // Check link mode + if (!S_ISREG(st.st_mode)) { + std::cerr << "ERROR: Target has wrong file type" << std::endl; + return -1; + } + // Check link size + if (st.st_size != strlen(buffIn)) { + std::cerr << "ERROR: Link has wrong size" << std::endl; + return -1; + } + + + /* Read the link back */ + fd = open(link_int.c_str(), O_RDONLY); + if (fd < 0) { + std::cerr << "ERROR: opening link (read): " << strerror(errno) << std::endl; + return -1; + } + auto nr = read(fd, buffOut, strlen(buffIn) + 1); + if (nr != strlen(buffIn)) { + std::cerr << "ERROR: reading link" << strerror(errno) << std::endl; + return -1; + } + if (strncmp(buffIn, buffOut, strlen(buffIn)) != 0) { + std::cerr << "ERROR: File content mismatch" << std::endl; + return -1; + } + ret = close(fd); + if (ret != 0) { + std::cerr << "ERROR: Error closing link: " << strerror(errno) << std::endl; + return -1; + }; + + + /* Remove link */ + ret = unlink(link_int.c_str()); + if (ret != 0) { + std::cerr << "Error removing link: " << strerror(errno) << std::endl; + return -1; + }; + + assert((lstat(link_int.c_str(), &st) == -1) && (errno == ENOENT)); + assert((stat(link_int.c_str(), &st) == -1) && (errno == ENOENT)); + + /* Remove target */ + ret = unlink(target_int.c_str()); + if (ret != 0) { + std::cerr << "Error removing link: " << strerror(errno) << std::endl; + return -1; + }; + + + // Clean test working directories + ret = rmdir(dir_int.c_str()); + if (ret != 0) { + std::cerr << "ERROR: cannot remove internal dir: " << strerror(errno) << std::endl; + return -1; + } + ret = rmdir(dir_ext.c_str()); + if (ret != 0) { + std::cerr << "ERROR: cannot remove internal dir: " << strerror(errno) << std::endl; + return -1; + } +}