diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fef21fef003a34613aae3c9c32b963801cad0c64..b8bd60cb476a697d111c357e3482fd1b2ef5a560 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -69,6 +69,7 @@ gkfs: -DGKFS_ENABLE_ROCKSDB:BOOL=ON -DGKFS_CHUNK_STATS:BOOL=ON -DGKFS_ENABLE_PROMETHEUS:BOOL=ON + -DGKFS_RENAME_SUPPORT:BOOL=ON ${CI_PROJECT_DIR} - make -j$(nproc) install # reduce artifacts size @@ -137,7 +138,7 @@ gkfs:integration: needs: ['gkfs'] parallel: matrix: - - SUBTEST: [ data, status, syscalls, directories, operations, position, shell ] + - SUBTEST: [ data, status, syscalls, directories, operations, position, shell, rename ] script: ## run tests diff --git a/CHANGELOG.md b/CHANGELOG.md index 6818f1da8a97e1cdf4eecfd7d12ba2d472c0fc51..7a0debe6f7e329d0e50e232bc1232754c22142c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Support for increasing file size via `truncate()` added ([!159](https://storage.bsc.es/gitlab/hpc/gekkofs/-/merge_requests/159) - Added PowerPC support ([!151](https://storage.bsc.es/gitlab/hpc/gekkofs/-/merge_requests/151)). +- GKFS_RENAME_SUPPORT adds support for renaming files. It includes the use case of renaming opened files using the fd +- FLOCK and fcntl functions for locks, are not supported, but they are available. ### Changed diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a41ef5e4184d8f721aabf2a8cae8fefeb41cb12..d266978613ffaae8904f3f5b28b445c62a2fb7ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,11 +147,19 @@ endif() option(CREATE_CHECK_PARENTS "Check parent directory existance before creating child node" ON) message(STATUS "[gekkofs] Create checks parents: ${CREATE_CHECK_PARENTS}") -option(SYMLINK_SUPPORT "Compile with support for symlinks" ON) -if (SYMLINK_SUPPORT) +option(GKFS_SYMLINK_SUPPORT "Compile with support for symlinks" OFF) +if (GKFS_SYMLINK_SUPPORT) add_definitions(-DHAS_SYMLINKS) endif () -message(STATUS "[gekkofs] Symlink support: ${SYMLINK_SUPPORT}") +option(GKFS_RENAME_SUPPORT "Compile with support for rename ops" OFF) +if (GKFS_RENAME_SUPPORT) + # Rename depends on symlink support + add_definitions(-DHAS_SYMLINKS) + set(GKFS_SYMLINK_SUPPORT ON) + add_definitions(-DHAS_RENAME) +endif () +message(STATUS "[gekkofs] Symlink support: ${GKFS_SYMLINK_SUPPORT}") +message(STATUS "[gekkofs] Rename support: ${GKFS_RENAME_SUPPORT}") set(MAX_INTERNAL_FDS 256 CACHE STRING "Number of file descriptors reserved for internal use") add_definitions(-DMAX_INTERNAL_FDS=${MAX_INTERNAL_FDS}) diff --git a/README.md b/README.md index 696aebdc99641051f3b4dfd494447ae1d0d1d80e..b712ce04343b1e2eaa7f693c912163cf0a1dc13c 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,7 @@ Then, the `examples/distributors/guided/generate.py` scrpt is used to create the Finally, modify `guided_config.txt` to your distribution requirements. -### Metadata Backends +## Metadata Backends There are two different metadata backends in GekkoFS. The default one uses `rocksdb`, however an alternative based on `PARALLAX` from `FORTH` @@ -279,7 +279,7 @@ with `-DGKFS_ENABLE_ROCKSDB:BOOL=OFF`. Once it is enabled, `--dbbackend` option will be functional. -### Statistics +## Statistics GekkoFS daemons are able to output general operations (`--enable-collection`) and data chunk statistics (`--enable-chunkstats`) to a specified output file via `--output-stats `. Prometheus can also be used @@ -287,7 +287,17 @@ instead or in addition to the output file. It must be enabled at compile time vi argument `-DGKFS_ENABLE_PROMETHEUS` and the daemon argument `--enable-prometheus`. The corresponding statistics are then pushed to the Prometheus instance. -### Acknowledgment +## Advanced experimental features + +### Rename + +`-DGKFS_RENAME_SUPPORT` allows the application to rename files. +This is an experimental feature, and some scenarios may not work properly. +Support for fstat in renamed files is included. + +This is disabled by default. + +## Acknowledgment This software was partially supported by the EC H2020 funded NEXTGenIO project (Project ID: 671951, www.nextgenio.eu). diff --git a/include/client/gkfs_functions.hpp b/include/client/gkfs_functions.hpp index d9d9d8d86455d4f357f3606ff2a18d8090841b96..437b052ef301b65790ba7e2b73fe8850ca26e94c 100644 --- a/include/client/gkfs_functions.hpp +++ b/include/client/gkfs_functions.hpp @@ -149,6 +149,10 @@ gkfs_getdents64(unsigned int fd, struct linux_dirent64* dirp, int gkfs_rmdir(const std::string& path); +#ifdef HAS_RENAME +int +gkfs_rename(const std::string& old_path, const std::string& new_path); +#endif // HAS_RENAME } // namespace gkfs::syscall // gkfs_getsingleserverdir is using extern "C" to demangle it for C usage diff --git a/include/client/hooks.hpp b/include/client/hooks.hpp index 6871d8b798f9c7a4e47e7bcc1988c0478552c608..95d0e7516d0d7214dcfd4d0001a37509a34aed72 100644 --- a/include/client/hooks.hpp +++ b/include/client/hooks.hpp @@ -120,6 +120,9 @@ hook_unlinkat(int dirfd, const char* cpath, int flags); int hook_symlinkat(const char* oldname, int newdfd, const char* newname); +int +hook_flock(unsigned long fd, int flags); + int hook_access(const char* path, int mask); diff --git a/include/client/rpc/forward_metadata.hpp b/include/client/rpc/forward_metadata.hpp index 8ece28e4a0f3f2e689c3d326970ad88575832d6e..564dc903b592ead71947a4696331214a323e685b 100644 --- a/include/client/rpc/forward_metadata.hpp +++ b/include/client/rpc/forward_metadata.hpp @@ -55,6 +55,12 @@ forward_create(const std::string& path, mode_t mode); int forward_stat(const std::string& path, std::string& attr); +#ifdef HAS_RENAME +int +forward_rename(const std::string& oldpath, const std::string& newpath, + const gkfs::metadata::Metadata& md); +#endif // HAS_RENAME + int forward_remove(const std::string& path); diff --git a/include/common/metadata.hpp b/include/common/metadata.hpp index 7939e5c477f205a03192227263beaf5f64771028..6332b511aeda17a30c384c38f5c583ebc5b8efe4 100644 --- a/include/common/metadata.hpp +++ b/include/common/metadata.hpp @@ -53,6 +53,10 @@ private: blkcnt_t blocks_{}; // allocated file system blocks_ #ifdef HAS_SYMLINKS std::string target_path_; // For links this is the path of the target file +#ifdef HAS_RENAME + std::string rename_path_; // In some cases fd is maintained so we need the + // renamed path +#endif #endif @@ -133,7 +137,15 @@ public: bool is_link() const; -#endif +#ifdef HAS_RENAME + std::string + rename_path() const; + + void + rename_path(const std::string& rename_path); +#endif // HAS_RENAME + +#endif // HAS_SYMLINKS }; } // namespace gkfs::metadata diff --git a/include/config.hpp b/include/config.hpp index 44f0bb60e99a64c138e3ea6b3e90c8ca6ad27254..ad162fb6abcb6dd2a6b6a8dfc0cba5efee0ed6bb 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -62,11 +62,16 @@ namespace metadata { constexpr auto dir = "metadata"; // which metadata should be considered apart from size and mode +// Blocks are used to store the rename status (-1 is a renamed file) constexpr auto use_atime = false; constexpr auto use_ctime = false; constexpr auto use_mtime = false; constexpr auto use_link_cnt = false; +#ifdef HAS_RENAME +constexpr auto use_blocks = true; +#else constexpr auto use_blocks = false; +#endif // HAS_RENAME /* * If true, all chunks on the same host are removed during a metadata remove * rpc. This is a technical optimization that reduces the number of RPCs for diff --git a/src/client/gkfs_functions.cpp b/src/client/gkfs_functions.cpp index 95a09f1c105224c4f288fbf116c2995522bf90e4..1d664ed5e77e8e2db785c0ab661f8830d61e9831 100644 --- a/src/client/gkfs_functions.cpp +++ b/src/client/gkfs_functions.cpp @@ -172,6 +172,15 @@ gkfs_open(const std::string& path, mode_t mode, int flags) { return -1; } md = *md_; +#ifdef HAS_RENAME + // This is an old file that was renamed which we do not open + if(md.blocks() == -1) { + LOG(DEBUG, + "This file was renamed and we do not open. path '{}'", + path); + return -1; + } +#endif // HAS_RENAME } else { LOG(ERROR, "Error creating file: '{}'", strerror(errno)); return -1; @@ -187,12 +196,13 @@ gkfs_open(const std::string& path, mode_t mode, int flags) { if(errno != ENOENT) { LOG(ERROR, "Error stating existing file '{}'", path); } - // file doesn't exists and O_CREAT was not set + // file doesn't exist and O_CREAT was not set return -1; } md = *md_; } + #ifdef HAS_SYMLINKS if(md.is_link()) { if(flags & O_NOFOLLOW) { @@ -202,13 +212,50 @@ gkfs_open(const std::string& path, mode_t mode, int flags) { } return gkfs_open(md.target_path(), mode, flags); } -#endif +#ifdef HAS_RENAME + auto new_path = path; + if(md.blocks() == -1) { + // This is an old file that was renamed and essentially no longer exists + errno = ENOENT; + return -1; + } else { + if(!md.target_path().empty()) { + // get renamed path from target and retrieve metadata from it + auto md_ = gkfs::utils::get_metadata(md.target_path()); + new_path = md.target_path(); + while(!md_.value().target_path().empty()) { + new_path = md_.value().target_path(); + md_ = gkfs::utils::get_metadata(md_.value().target_path(), + false); + if(!md_) { + return -1; + } + } + md = *md_; + if(S_ISDIR(md.mode())) { + return gkfs_opendir(new_path); + } + + /*** Regular file exists ***/ + assert(S_ISREG(md.mode())); + + if((flags & O_TRUNC) && ((flags & O_RDWR) || (flags & O_WRONLY))) { + if(gkfs_truncate(new_path, md.size(), 0)) { + LOG(ERROR, "Error truncating file"); + return -1; + } + } + return CTX->file_map()->add( + std::make_shared(new_path, flags)); + } + } +#endif // HAS_RENAME +#endif // HAS_SYMLINKS if(S_ISDIR(md.mode())) { return gkfs_opendir(path); } - /*** Regular file exists ***/ assert(S_ISREG(md.mode())); @@ -283,6 +330,31 @@ gkfs_remove(const std::string& path) { errno = EISDIR; return -1; } +#ifdef HAS_SYMLINKS +#ifdef HAS_RENAME + if(md.value().blocks() == -1) { + errno = ENOENT; + return -1; + } else { + if(!md.value().target_path().empty()) { + auto md_ = gkfs::utils::get_metadata(md.value().target_path()); + std::string new_path = md.value().target_path(); + while(!md.value().target_path().empty()) { + new_path = md.value().target_path(); + md = gkfs::utils::get_metadata(md.value().target_path(), false); + if(!md) { + return -1; + } + } + auto err = gkfs::rpc::forward_remove(new_path); + if(err) { + errno = err; + return -1; + } + } + } +#endif // HAS_RENAME +#endif // HAS_SYMLINKS auto err = gkfs::rpc::forward_remove(path); if(err) { @@ -304,10 +376,104 @@ int gkfs_access(const std::string& path, const int mask, bool follow_links) { auto md = gkfs::utils::get_metadata(path, follow_links); if(!md) { + LOG(DEBUG, "File does not exist '{}'", path); + return -1; + } +#ifdef HAS_SYMLINKS +#ifdef HAS_RENAME + LOG(DEBUG, "Checking for renamed file '{}'", path); + if(md.value().blocks() == -1) { + errno = ENOENT; + LOG(DEBUG, "File exist but it is renamed '{}'", path); + return -1; + + } else { + while(!md.value().target_path().empty()) { + LOG(DEBUG, "File exist but it is renamed '{} -> {}'", path, + md.value().target_path()); + md = gkfs::utils::get_metadata(md.value().target_path(), false); + if(!md) { + LOG(DEBUG, "File does not exist but it is renamed '{} -> {}'", + path, md.value().target_path()); + return -1; + } + } + } +#endif // HAS_RENAME +#endif // HAS_SYMLINKS + return 0; +} + +#ifdef HAS_SYMLINKS +#ifdef HAS_RENAME +/** + * gkfs wrapper for rename() system calls + * errno may be set + * We use blocks to determine if the file is a renamed file. + * If the file is re-renamed (a->b->a) a recovers the block of b + * and we delete b. + * @param old_path + * @param new_path + * @return 0 on success, -1 on failure + */ +int +gkfs_rename(const string& old_path, const string& new_path) { + auto md_old = gkfs::utils::get_metadata(old_path, false); + + // if the file is not found, or it is a renamed one cancel. + if(!md_old || md_old.value().blocks() == -1) { + return -1; + } + auto md_new = gkfs::utils::get_metadata(new_path, false); + if(md_new) { + // the new file exists... check for circular... + if(md_new.value().blocks() == -1 && + md_old.value().target_path() == new_path) { + // the new file is a renamed file, so we need to get the metadata of + // the original file. + LOG(DEBUG, "Destroying Circular Rename '{}' --> '{}'", old_path, + new_path); + gkfs::metadata::MetadentryUpdateFlags flags{}; + flags.atime = false; + flags.mtime = false; + flags.ctime = false; + flags.blocks = true; + flags.mode = false; + flags.size = false; + flags.uid = false; + flags.gid = false; + flags.link_count = false; + md_old.value().blocks(0); + md_old.value().target_path(""); + + auto err = gkfs::rpc::forward_update_metadentry( + new_path, md_old.value(), flags); + + if(err) { + errno = err; + return -1; + } + // Delete old file + err = gkfs::rpc::forward_remove(old_path); + if(err) { + errno = err; + return -1; + } + return 0; + } return -1; } + + auto err = gkfs::rpc::forward_rename(old_path, new_path, md_old.value()); + if(err) { + errno = err; + return -1; + } + return 0; } +#endif +#endif /** * gkfs wrapper for stat() system calls @@ -323,6 +489,21 @@ gkfs_stat(const string& path, struct stat* buf, bool follow_links) { if(!md) { return -1; } +#ifdef HAS_SYMLINKS +#ifdef HAS_RENAME + if(md.value().blocks() == -1) { + errno = ENOENT; + return -1; + } else { + while(!md.value().target_path().empty()) { + md = gkfs::utils::get_metadata(md.value().target_path(), false); + if(!md) { + return -1; + } + } + } +#endif +#endif gkfs::utils::metadata_to_stat(path, *md, *buf); return 0; } @@ -344,10 +525,25 @@ int gkfs_statx(int dirfs, const std::string& path, int flags, unsigned int mask, struct statx* buf, bool follow_links) { auto md = gkfs::utils::get_metadata(path, follow_links); + if(!md) { return -1; } - +#ifdef HAS_SYMLINKS +#ifdef HAS_RENAME + if(md.value().blocks() == -1) { + errno = ENOENT; + return -1; + } else { + while(!md.value().target_path().empty()) { + md = gkfs::utils::get_metadata(md.value().target_path(), false); + if(!md) { + return -1; + } + } + } +#endif +#endif struct stat tmp {}; gkfs::utils::metadata_to_stat(path, *md, tmp); @@ -577,6 +773,31 @@ gkfs_truncate(const std::string& path, off_t length) { return -1; } + // If rename is enabled we need to check if the file is renamed +#ifdef HAS_SYMLINKS +#ifdef HAS_RENAME + if(md.value().blocks() == -1) { + errno = ENOENT; + return -1; + } else if(!md.value().target_path().empty()) { + std::string new_path; + while(!md.value().target_path().empty()) { + new_path = md.value().target_path(); + md = gkfs::utils::get_metadata(md.value().target_path()); + } + // This could be optimized + auto size = md->size(); + if(static_cast(length) > size) { + LOG(DEBUG, "Length is greater then file size: {} > {}", length, + size); + errno = EINVAL; + return -1; + } + return gkfs_truncate(new_path, size, length); + } +#endif +#endif + auto size = md->size(); if(static_cast(length) > size) { LOG(DEBUG, "Length is greater then file size: '{}' > '{}'", length, diff --git a/src/client/hooks.cpp b/src/client/hooks.cpp index 49a166ce1988ff3f605b75b8da10c124290c116b..fa557a1f81490e8925fe635b9747e050097adf44 100644 --- a/src/client/hooks.cpp +++ b/src/client/hooks.cpp @@ -178,6 +178,14 @@ hook_fstat(unsigned int fd, struct stat* buf) { if(CTX->file_map()->exist(fd)) { auto path = CTX->file_map()->get(fd)->path(); +#ifdef HAS_RENAME + // Special case for fstat and rename, fd points to new file... + // We can change file_map and recall + auto md = gkfs::utils::get_metadata(path, false); + if(md.has_value() && md.value().blocks() == -1) { + path = md.value().rename_path(); + } +#endif return with_errno(gkfs::syscall::gkfs_stat(path, buf)); } return syscall_no_intercept_wrapper(SYS_fstat, fd, buf); @@ -395,6 +403,16 @@ hook_symlinkat(const char* oldname, int newdfd, const char* newname) { } } +int +hook_flock(unsigned long fd, int flags) { + LOG(ERROR, "{}() called flock (Not Supported) with fd '{}' flags '{}'", + __func__, fd, flags); + + if(CTX->file_map()->exist(fd)) { + return 0; + } else + return -EBADF; +} int hook_access(const char* path, int mask) { @@ -831,6 +849,13 @@ hook_fcntl(unsigned int fd, unsigned int cmd, unsigned long arg) { gkfs::filemap::OpenFile_flags::cloexec, (arg & FD_CLOEXEC)); return 0; + case F_GETLK: + LOG(ERROR, "{}() F_GETLK on fd (Not Supported) {}", __func__, fd); + return 0; + + case F_SETLK: + LOG(ERROR, "{}() F_SETLK on fd (Not Supported) {}", __func__, fd); + return 0; default: LOG(ERROR, "{}() unrecognized command {} on fd {}", __func__, cmd, @@ -865,8 +890,7 @@ hook_renameat(int olddfd, const char* oldname, int newdfd, const char* newname, return -ENOTDIR; case gkfs::preload::RelativizeStatus::internal: - LOG(WARNING, "{}() not supported", __func__); - return -ENOTSUP; + break; default: LOG(ERROR, "{}() relativize status unknown", __func__); @@ -890,9 +914,16 @@ hook_renameat(int olddfd, const char* oldname, int newdfd, const char* newname, return -ENOTDIR; case gkfs::preload::RelativizeStatus::internal: - LOG(WARNING, "{}() not supported", __func__); +#ifdef HAS_RENAME + if(oldpath_status == gkfs::preload::RelativizeStatus::internal) { + return with_errno(gkfs::syscall::gkfs_rename(oldpath_resolved, + newpath_resolved)); + } else { + return -ENOTSUP; + } +#else return -ENOTSUP; - +#endif default: LOG(ERROR, "{}() relativize status unknown", __func__); return -EINVAL; diff --git a/src/client/intercept.cpp b/src/client/intercept.cpp index fce6e79c6dc26093d1a0906d6adebcdb11a3b44c..f962d97bb0713cb2e194d570a46f4408bb3608c0 100644 --- a/src/client/intercept.cpp +++ b/src/client/intercept.cpp @@ -683,6 +683,10 @@ hook(long syscall_number, long arg0, long arg1, long arg2, long arg3, long arg4, static_cast(arg2)); break; + case SYS_flock: + *result = gkfs::hook::hook_flock(static_cast(arg0), + static_cast(arg1)); + break; case SYS_chdir: *result = gkfs::hook::hook_chdir(reinterpret_cast(arg0)); @@ -747,6 +751,7 @@ hook(long syscall_number, long arg0, long arg1, long arg2, long arg3, long arg4, reinterpret_cast(arg1)); break; + case SYS_fdatasync: case SYS_fsync: *result = gkfs::hook::hook_fsync(static_cast(arg0)); break; diff --git a/src/client/rpc/forward_metadata.cpp b/src/client/rpc/forward_metadata.cpp index dd76a638e04024d5a1fa5017f7835d815f9a24e0..a087590c807acfe33697775931f816c8f58cddd5 100644 --- a/src/client/rpc/forward_metadata.cpp +++ b/src/client/rpc/forward_metadata.cpp @@ -341,6 +341,141 @@ forward_update_metadentry( } } +#ifdef HAS_RENAME +/** + * Send an RPC for a rename metadentry request. + * Steps.. SetUp a blkcnt of -1 + * This marks that this file doesn't have to be accessed directly + * Create a new md with the new name, which should have as value the old name + * All operations should check blockcnt and extract a NOTEXISTS + * @param oldpath + * @param newpath + * @param md + * + * @return error code + */ +int +forward_rename(const string& oldpath, const string& newpath, + const gkfs::metadata::Metadata& md) { + + auto endp = + CTX->hosts().at(CTX->distributor()->locate_file_metadata(oldpath)); + + try { + LOG(DEBUG, "Sending RPC ..."); + // TODO(amiranda): add a post() with RPC_TIMEOUT to hermes so that we + // can retry for RPC_TRIES (see old commits with margo) + // TODO(amiranda): hermes will eventually provide a post(endpoint) + // returning one result and a broadcast(endpoint_set) returning a + // result_set. When that happens we can remove the .at(0) :/ + auto out = ld_network_service + ->post( + endp, oldpath, (md.link_count()), + /* mode */ 0, + /* uid */ 0, + /* gid */ 0, md.size(), + /* blockcnt */ -1, (md.atime()), + (md.mtime()), (md.ctime()), + bool_to_merc_bool(md.link_count()), + /* mode_flag */ false, + bool_to_merc_bool(md.size()), 1, + bool_to_merc_bool(md.atime()), + bool_to_merc_bool(md.mtime()), + bool_to_merc_bool(md.ctime())) + .get() + .at(0); + + LOG(DEBUG, "Got response success: {}", out.err()); + + // Now create the new file + + } catch(const std::exception& ex) { + LOG(ERROR, "while getting rpc output"); + return EBUSY; + } + + auto md2 = md; + + md2.target_path(oldpath); + /* + * Now create the new file + */ + // TODO(amiranda): add a post() with RPC_TIMEOUT to hermes so that we + // can retry for RPC_TRIES (see old commits with margo) + // TODO(amiranda): hermes will eventually provide a post(endpoint) + // returning one result and a broadcast(endpoint_set) returning a + // result_set. When that happens we can remove the .at(0) :/ + auto endp2 = + CTX->hosts().at(CTX->distributor()->locate_file_metadata(newpath)); + + try { + LOG(DEBUG, "Sending RPC ..."); + // TODO(amiranda): add a post() with RPC_TIMEOUT to hermes so that we + // can retry for RPC_TRIES (see old commits with margo) + // TODO(amiranda): hermes will eventually provide a post(endpoint) + // returning one result and a broadcast(endpoint_set) returning a + // result_set. When that happens we can remove the .at(0) :/ + + auto out = ld_network_service + ->post(endp2, newpath, md2.mode()) + .get() + .at(0); + LOG(DEBUG, "Got response success: {}", out.err()); + + } catch(const std::exception& ex) { + LOG(ERROR, "while getting rpc output"); + return EBUSY; + } + + + try { + LOG(DEBUG, "Sending RPC ..."); + // TODO(amiranda): add a post() with RPC_TIMEOUT to hermes so that we + // can retry for RPC_TRIES (see old commits with margo) + // TODO(amiranda): hermes will eventually provide a post(endpoint) + // returning one result and a broadcast(endpoint_set) returning a + // result_set. When that happens we can remove the .at(0) :/ + // Update new file with target link = oldpath + auto out = + ld_network_service + ->post(endp2, newpath, oldpath) + .get() + .at(0); + + LOG(DEBUG, "Got response success: {}", out.err()); + + // return out.err() ? out.err() : 0; + + } catch(const std::exception& ex) { + LOG(ERROR, "while getting rpc output"); + return EBUSY; + } + + // Update the renamed path to solve the issue with fstat with fd) + try { + LOG(DEBUG, "Sending RPC ..."); + // TODO(amiranda): add a post() with RPC_TIMEOUT to hermes so that we + // can retry for RPC_TRIES (see old commits with margo) + // TODO(amiranda): hermes will eventually provide a post(endpoint) + // returning one result and a broadcast(endpoint_set) returning a + // result_set. When that happens we can remove the .at(0) :/ + auto out = ld_network_service + ->post(endp, oldpath, newpath) + .get() + .at(0); + + LOG(DEBUG, "Got response success: {}", out.err()); + + // return out.err() ? out.err() : 0; + return 0; + } catch(const std::exception& ex) { + LOG(ERROR, "while getting rpc output"); + return EBUSY; + } +} + +#endif + /** * Send an RPC request for an update to the file size. * This is called during a write() call or similar diff --git a/src/common/metadata.cpp b/src/common/metadata.cpp index d75f6681bbd27172f24755a8ab1cee1323178b27..d5836216a99f4ebe8797508463df5f59e3c84b22 100644 --- a/src/common/metadata.cpp +++ b/src/common/metadata.cpp @@ -109,7 +109,7 @@ Metadata::Metadata(const std::string& binary_str) { // encounter a // delimiter anymore assert(*ptr == MSP); - blocks_ = static_cast(std::stoul(++ptr, &read)); + blocks_ = static_cast(std::stol(++ptr, &read)); assert(read > 0); ptr += read; } @@ -119,9 +119,20 @@ Metadata::Metadata(const std::string& binary_str) { assert(*ptr == MSP); target_path_ = ++ptr; // target_path should be there only if this is a link - assert(target_path_.empty() || S_ISLNK(mode_)); ptr += target_path_.size(); -#endif +#ifdef HAS_RENAME + // Read rename target, we had captured '|' so we need to recover it + if(!target_path_.empty()) { + auto index = target_path_.find_last_of(MSP); + auto size = target_path_.size(); + target_path_ = target_path_.substr(0, index); + ptr -= (size - index); + } + assert(*ptr == MSP); + rename_path_ = ++ptr; + ptr += rename_path_.size(); +#endif // HAS_RENAME +#endif // HAS_SYMLINKS // we consumed all the binary string assert(*ptr == '\0'); @@ -158,7 +169,11 @@ Metadata::serialize() const { #ifdef HAS_SYMLINKS s += MSP; s += target_path_; -#endif +#ifdef HAS_RENAME + s += MSP; + s += rename_path_; +#endif // HAS_RENAME +#endif // HAS_SYMLINKS return s; } @@ -260,16 +275,11 @@ Metadata::blocks(blkcnt_t blocks) { 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; } @@ -278,6 +288,18 @@ Metadata::is_link() const { return S_ISLNK(mode_); } -#endif +#ifdef HAS_RENAME +std::string +Metadata::rename_path() const { + return rename_path_; +} + +void +Metadata::rename_path(const std::string& rename_path) { + rename_path_ = rename_path; +} + +#endif // HAS_RENAME +#endif // HAS_SYMLINKS } // namespace gkfs::metadata diff --git a/src/daemon/backend/metadata/parallax_backend.cpp b/src/daemon/backend/metadata/parallax_backend.cpp index 4cb02b8c8c41badaa38bb08d47c116f7ee1112ca..484f58da4d73a873465200ea9dfd0163fa1d2b34 100644 --- a/src/daemon/backend/metadata/parallax_backend.cpp +++ b/src/daemon/backend/metadata/parallax_backend.cpp @@ -415,6 +415,15 @@ ParallaxBackend::get_dirents_impl(const std::string& dir) const { assert(!name.empty()); Metadata md(v); +#ifdef HAS_RENAME + // Remove entries with negative blocks (rename) + if(md.blocks() == -1) { + if(par_get_next(S) && !par_is_valid(S)) + break; + else + continue; + } +#endif // HAS_RENAME auto is_dir = S_ISDIR(md.mode()); entries.emplace_back(std::move(name), is_dir); @@ -489,6 +498,15 @@ ParallaxBackend::get_dirents_extended_impl(const std::string& dir) const { assert(!name.empty()); Metadata md(v); +#ifdef HAS_RENAME + // Remove entries with negative blocks (rename) + if(md.blocks() == -1) { + if(par_get_next(S) && !par_is_valid(S)) + break; + else + continue; + } +#endif // HAS_RENAME auto is_dir = S_ISDIR(md.mode()); entries.emplace_back(std::forward_as_tuple(std::move(name), is_dir, diff --git a/src/daemon/backend/metadata/rocksdb_backend.cpp b/src/daemon/backend/metadata/rocksdb_backend.cpp index 0a4deca4d926ad03169843cdaeb2bbca8158fe93..99a0263d13c0cfe4eb06c2a539525af61c382090 100644 --- a/src/daemon/backend/metadata/rocksdb_backend.cpp +++ b/src/daemon/backend/metadata/rocksdb_backend.cpp @@ -265,6 +265,12 @@ RocksDBBackend::get_dirents_impl(const std::string& dir) const { assert(!name.empty()); Metadata md(it->value().ToString()); +#ifdef HAS_RENAME + // Remove entries with negative blocks (rename) + if(md.blocks() == -1) { + continue; + } +#endif // HAS_RENAME auto is_dir = S_ISDIR(md.mode()); entries.emplace_back(std::move(name), is_dir); @@ -309,6 +315,12 @@ RocksDBBackend::get_dirents_extended_impl(const std::string& dir) const { assert(!name.empty()); Metadata md(it->value().ToString()); +#ifdef HAS_RENAME + // Remove entries with negative blocks (rename) + if(md.blocks() == -1) { + continue; + } +#endif // HAS_RENAME auto is_dir = S_ISDIR(md.mode()); entries.emplace_back(std::forward_as_tuple(std::move(name), is_dir, diff --git a/src/daemon/handler/srv_metadata.cpp b/src/daemon/handler/srv_metadata.cpp index eb10456281a9d1fa5fe144b2ea50bd68c83d4782..7dcec929d326071cc519b2969fe1ec1314ed4b0a 100644 --- a/src/daemon/handler/srv_metadata.cpp +++ b/src/daemon/handler/srv_metadata.cpp @@ -828,9 +828,9 @@ rpc_srv_get_dirents_extended(hg_handle_t handle) { return gkfs::rpc::cleanup_respond(&handle, &in, &out, &bulk_handle); } -#ifdef HAS_SYMLINKS +#if defined(HAS_SYMLINKS) || defined(HAS_RENAME) /** - * @brief Serves a request create a symbolic link. This function is UNUSED. + * @brief Serves a request create a symbolic link and supports rename * @internal * The state of this function is unclear and requires a complete refactor. * @@ -850,20 +850,34 @@ rpc_srv_mk_symlink(hg_handle_t handle) { GKFS_DATA->spdlogger()->error( "{}() Failed to retrieve input from handle", __func__); } - GKFS_DATA->spdlogger()->debug("{}() Got RPC with path '{}'", __func__, - in.path); - + GKFS_DATA->spdlogger()->debug( + "{}() Got RPC with path '{}' and target path '{}'", __func__, + in.path, in.target_path); + // do update try { - gkfs::metadata::Metadata md = {gkfs::metadata::LINK_MODE, - in.target_path}; - // create metadentry - gkfs::metadata::create(in.path, md); + gkfs::metadata::Metadata md = gkfs::metadata::get(in.path); +#ifdef HAS_RENAME + if(md.blocks() == -1) { + // We need to fill the rename path as this is an inverse path + // old -> new + md.rename_path(in.target_path); + } else { +#endif // HAS_RENAME + md.target_path(in.target_path); +#ifdef HAS_RENAME + } +#endif // HAS_RENAME + GKFS_DATA->spdlogger()->debug( + "{}() Updating path '{}' with metadata '{}'", __func__, in.path, + md.serialize()); + gkfs::metadata::update(in.path, md); out.err = 0; } catch(const std::exception& e) { - GKFS_DATA->spdlogger()->error("{}() Failed to create metadentry: '{}'", - __func__, e.what()); - out.err = -1; + // TODO handle NotFoundException + GKFS_DATA->spdlogger()->error("{}() Failed to update entry", __func__); + out.err = 1; } + GKFS_DATA->spdlogger()->debug("{}() Sending output err '{}'", __func__, out.err); auto hret = margo_respond(handle, &out); @@ -877,7 +891,7 @@ rpc_srv_mk_symlink(hg_handle_t handle) { return HG_SUCCESS; } -#endif +#endif // HAS_SYMLINKS || HAS_RENAME } // namespace diff --git a/tests/integration/CMakeLists.txt b/tests/integration/CMakeLists.txt index 775cfe4f3d720b0336ff70b14989e87a66818200..9e76e90c554d3175f29489e19941c85c09b6c825 100644 --- a/tests/integration/CMakeLists.txt +++ b/tests/integration/CMakeLists.txt @@ -127,6 +127,15 @@ gkfs_add_python_test( SOURCE syscalls/ ) +if (GKFS_RENAME_SUPPORT) +gkfs_add_python_test( + NAME test_rename + PYTHON_VERSION 3.6 + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests/integration + SOURCE rename/ +) +endif() + if(GKFS_INSTALL_TESTS) install(DIRECTORY harness DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/gkfs/tests/integration @@ -137,7 +146,7 @@ if(GKFS_INSTALL_TESTS) PATTERN "gkfs.io" EXCLUDE ) - if(SYMLINK_SUPPORT) + if(GKFS_SYMLINK_SUPPORT) install(DIRECTORY directories DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/gkfs/tests/integration FILES_MATCHING @@ -214,7 +223,15 @@ if(GKFS_INSTALL_TESTS) ) endif () - + if (GKFS_RENAME_SUPPORT) + install(DIRECTORY rename + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/gkfs/tests/integration + FILES_MATCHING + REGEX ".*\\.py" + PATTERN "__pycache__" EXCLUDE + PATTERN ".pytest_cache" EXCLUDE + ) + endif() endif() diff --git a/tests/integration/harness/CMakeLists.txt b/tests/integration/harness/CMakeLists.txt index 39bd95bb26b8965d85d4dea44327e096fbb943d7..25440c4a48e06da670d7cd2083ace0a0fea536d2 100644 --- a/tests/integration/harness/CMakeLists.txt +++ b/tests/integration/harness/CMakeLists.txt @@ -67,6 +67,7 @@ add_executable(gkfs.io gkfs.io/statfs.cpp gkfs.io/dup_validate.cpp gkfs.io/syscall_coverage.cpp + gkfs.io/rename.cpp ) include(FetchContent) diff --git a/tests/integration/harness/gkfs.io/commands.hpp b/tests/integration/harness/gkfs.io/commands.hpp index c57ef5c40dcc3a6ba5d95c0f7c3895d476ab8df4..ab1c83f1a2d1515d80149dabe1f4e33b6d690332 100644 --- a/tests/integration/harness/gkfs.io/commands.hpp +++ b/tests/integration/harness/gkfs.io/commands.hpp @@ -123,4 +123,8 @@ dup_validate_init(CLI::App& app); void syscall_coverage_init(CLI::App& app); +void +rename_init(CLI::App& app); + + #endif // IO_COMMANDS_HPP diff --git a/tests/integration/harness/gkfs.io/main.cpp b/tests/integration/harness/gkfs.io/main.cpp index 91514bba3e2ae7d042ca2607f2d113d68ce4857d..91911d5006b880d19f18914905303d6483666c43 100644 --- a/tests/integration/harness/gkfs.io/main.cpp +++ b/tests/integration/harness/gkfs.io/main.cpp @@ -67,6 +67,7 @@ init_commands(CLI::App& app) { unlink_init(app); dup_validate_init(app); syscall_coverage_init(app); + rename_init(app); } diff --git a/tests/integration/harness/gkfs.io/rename.cpp b/tests/integration/harness/gkfs.io/rename.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c47a232510bd59e79591c5b81d20a3d58424a169 --- /dev/null +++ b/tests/integration/harness/gkfs.io/rename.cpp @@ -0,0 +1,110 @@ +/* + Copyright 2018-2021, Barcelona Supercomputing Center (BSC), Spain + Copyright 2015-2021, Johannes Gutenberg Universitaet Mainz, Germany + + This software was partially supported by the + EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu). + + This software was partially supported by the + ADA-FS project under the SPPEXA project funded by the DFG. + + This file is part of GekkoFS. + + GekkoFS 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, either version 3 of the License, or + (at your option) any later version. + + GekkoFS is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with GekkoFS. If not, see . + + SPDX-License-Identifier: GPL-3.0-or-later +*/ + +/* C++ includes */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* C includes */ +#include +#include +#include +#include + +using json = nlohmann::json; + +struct rename_options { + bool verbose{}; + std::string pathname; + std::string pathname2; + + REFL_DECL_STRUCT(rename_options, REFL_DECL_MEMBER(bool, verbose), + REFL_DECL_MEMBER(std::string, pathname), + REFL_DECL_MEMBER(std::string, pathname2)); +}; + +struct rename_output { + ::off_t retval; + int errnum; + + REFL_DECL_STRUCT(rename_output, REFL_DECL_MEMBER(::off_t, retval), + REFL_DECL_MEMBER(int, errnum)); +}; + +void +to_json(json& record, const rename_output& out) { + record = serialize(out); +} + + +void +rename_exec(const rename_options& opts) { + + int fd = ::rename(opts.pathname.c_str(), opts.pathname2.c_str()); + + if(fd == -1) { + if(opts.verbose) { + fmt::print("rename(pathname=\"{}\", pathname2=\"{}\") = {}, errno: {} [{}]\n", + opts.pathname, opts.pathname2, fd, errno, ::strerror(errno)); + return; + } + } + + json out = rename_output{fd, errno}; + fmt::print("{}\n", out.dump(2)); + + return; +} + +void +rename_init(CLI::App& app) { + + // Create the option and subcommand objects + auto opts = std::make_shared(); + auto* cmd = app.add_subcommand("rename", "Execute the rename() system call"); + + // Add options to cmd, binding them to opts + cmd->add_flag("-v,--verbose", opts->verbose, + "Produce human writeable output"); + + cmd->add_option("pathname", opts->pathname, "old name") + ->required() + ->type_name(""); + + cmd->add_option("pathname2", opts->pathname2, "new name") + ->required() + ->type_name(""); + + cmd->callback([opts]() { rename_exec(*opts); }); +} diff --git a/tests/integration/harness/gkfs.io/syscall_coverage.cpp b/tests/integration/harness/gkfs.io/syscall_coverage.cpp index ddb25cf5dc896f9cb0a6844fad30ad474c5e3567..9c7a55467d2e55b9a809d5000f517101ec91bca7 100644 --- a/tests/integration/harness/gkfs.io/syscall_coverage.cpp +++ b/tests/integration/harness/gkfs.io/syscall_coverage.cpp @@ -366,21 +366,25 @@ syscall_coverage_exec(const syscall_coverage_options& opts) { return; } + std::string pid = std::to_string(getpid()); + std::string path1 = "/tmp/"+pid+"test_rename"; + std::string path2 = "/tmp/"+pid+"test_rename2"; + // renameat external - auto fdtmp = ::open("/tmp/test_rename", O_CREAT | O_WRONLY, 0644); + auto fdtmp = ::open(path1.c_str(), O_CREAT | O_WRONLY, 0644); ::close(fdtmp); - rv = ::renameat(AT_FDCWD, "/tmp/test_rename", AT_FDCWD, + rv = ::renameat(AT_FDCWD, path1.c_str(), AT_FDCWD, opts.pathname.c_str()); if(errno != ENOTSUP) { - output("renameat", rv, opts); + output("renameat_ext_to_int", rv, opts); return; } - rv = ::renameat(AT_FDCWD, "/tmp/test_rename", AT_FDCWD, - "/tmp/test_rename2"); + rv = ::renameat(AT_FDCWD, path1.c_str(), AT_FDCWD, + path2.c_str()); if(rv < 0) { - output("renameat", rv, opts); + output("renameat_ext_to_ext", rv, opts); return; } // sys_open diff --git a/tests/integration/harness/io.py b/tests/integration/harness/io.py index 834174a41a3addeeb291122f3de6b60a6fb6786c..d6a6c0ddc807efeb3c10e015beb71f1c75532cea 100644 --- a/tests/integration/harness/io.py +++ b/tests/integration/harness/io.py @@ -465,7 +465,14 @@ class FileCompareOutputSchema(Schema): def make_object(self, data, **kwargs): return namedtuple('FileCompareReturn', ['retval', 'errno'])(**data) +class RenameOutputSchema(Schema): + """Schema to deserialize the results of an rename() execution""" + retval = fields.Integer(required=True) + errno = Errno(data_key='errnum', required=True) + @post_load + def make_object(self, data, **kwargs): + return namedtuple('RenameReturn', ['retval', 'errno'])(**data) class IOParser: @@ -493,6 +500,7 @@ class IOParser: 'unlink' : UnlinkOutputSchema(), 'access' : AccessOutputSchema(), 'statfs' : StatfsOutputSchema(), + 'rename' : RenameOutputSchema(), # UTIL 'file_compare': FileCompareOutputSchema(), 'chdir' : ChdirOutputSchema(), @@ -500,6 +508,7 @@ class IOParser: 'symlink' : SymlinkOutputSchema(), 'dup_validate' : DupValidateOutputSchema(), 'syscall_coverage' : SyscallCoverageOutputSchema(), + } def parse(self, command, output): diff --git a/tests/integration/rename/test_rename_operation.py b/tests/integration/rename/test_rename_operation.py new file mode 100644 index 0000000000000000000000000000000000000000..97ab88b11f7acb812746d09439190238596bdc7d --- /dev/null +++ b/tests/integration/rename/test_rename_operation.py @@ -0,0 +1,397 @@ +################################################################################ +# Copyright 2018-2022, Barcelona Supercomputing Center (BSC), Spain # +# Copyright 2015-2022, Johannes Gutenberg Universitaet Mainz, Germany # +# # +# This software was partially supported by the # +# EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu). # +# # +# This software was partially supported by the # +# ADA-FS project under the SPPEXA project funded by the DFG. # +# # +# This file is part of GekkoFS. # +# # +# GekkoFS 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, either version 3 of the License, or # +# (at your option) any later version. # +# # +# GekkoFS is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with GekkoFS. If not, see . # +# # +# SPDX-License-Identifier: GPL-3.0-or-later # +################################################################################ + +import harness +from pathlib import Path +import errno +import stat +import os +import ctypes +import sys +import pytest +from harness.logger import logger + +nonexisting = "nonexisting" + + +def test_rename(gkfs_daemon, gkfs_client): + + file = gkfs_daemon.mountdir / "file" + file2 = gkfs_daemon.mountdir / "file2" + + # create a file in gekkofs + ret = gkfs_client.open(file, + os.O_CREAT | os.O_WRONLY, + stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) + + assert ret.retval == 10000 + + ret = gkfs_client.access(file, os.R_OK) + assert ret.retval == 0 + + # write a buffer we know + buf = b'42' + ret = gkfs_client.write(file, buf, len(buf)) + + assert ret.retval == len(buf) # Return the number of written bytes + + ret = gkfs_client.stat(file) + assert ret.retval != 1 + + ret = gkfs_client.stat(file2) + assert ret.retval == -1 + + ret = gkfs_client.rename(file, file2) + assert ret.retval == 0 + + ret = gkfs_client.access(file, os.R_OK) + assert ret.retval == -1 + + ret = gkfs_client.access(file2, os.R_OK) + assert ret.retval == 0 + + ret = gkfs_client.stat(file) + assert ret.retval == -1 + + ret = gkfs_client.stat(file2) + assert ret.retval != 1 + + # File is renamed, and innacesible + + # Read contents of file2 + ret = gkfs_client.open(file2, os.O_RDONLY) + assert ret.retval == 10000 + + ret = gkfs_client.read(file2, len(buf)) + assert ret.retval == len(buf) + assert ret.buf == buf + + + +def test_rename_inverse(gkfs_daemon, gkfs_client): + + file3 = gkfs_daemon.mountdir / "file3" + file4 = gkfs_daemon.mountdir / "file4" + + # create a file in gekkofs + ret = gkfs_client.open(file3, + os.O_CREAT | os.O_WRONLY, + stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) + + assert ret.retval == 10000 + + + + ret = gkfs_client.stat(file3) + assert ret.retval != 1 + + ret = gkfs_client.stat(file4) + assert ret.retval == -1 + + ret = gkfs_client.rename(file3, file4) + + assert ret.retval == 0 + + ret = gkfs_client.stat(file3) + assert ret.retval == -1 + + ret = gkfs_client.stat(file4) + assert ret.retval != 1 + + # File is renamed, and innacesible + + # write a buffer we know + buf = b'42' + ret = gkfs_client.write(file4, buf, len(buf)) + + assert ret.retval == len(buf) # Return the number of written bytes + + # Read contents of file2 + ret = gkfs_client.open(file4, os.O_RDONLY) + assert ret.retval == 10000 + + ret = gkfs_client.read(file4, len(buf)) + assert ret.retval == len(buf) + assert ret.buf == buf + + # It should work but the data should be on file 2 really + +def test_chain_rename(gkfs_daemon, gkfs_client): + + filea = gkfs_daemon.mountdir / "filea" + fileb = gkfs_daemon.mountdir / "fileb" + filec = gkfs_daemon.mountdir / "filec" + filed = gkfs_daemon.mountdir / "filed" + filee = gkfs_daemon.mountdir / "filee" + + # create a file in gekkofs + ret = gkfs_client.open(filea, + os.O_CREAT | os.O_WRONLY, + stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) + + assert ret.retval == 10000 + + # write a buffer we know + buf = b'42' + ret = gkfs_client.write(filea, buf, len(buf)) + + assert ret.retval == len(buf) # Return the number of written bytes + + ret = gkfs_client.stat(filea) + assert ret.retval != 1 + + ret = gkfs_client.stat(fileb) + assert ret.retval == -1 + + ret = gkfs_client.rename(filea, fileb) + + assert ret.retval == 0 + + ret = gkfs_client.stat(filea) + assert ret.retval == -1 + + ret = gkfs_client.stat(fileb) + assert ret.retval != 1 + + # File is renamed, and innacesible + + # Read contents of file2 + ret = gkfs_client.open(fileb, os.O_RDONLY) + assert ret.retval == 10000 + + ret = gkfs_client.read(fileb, len(buf)) + assert ret.retval == len(buf) + assert ret.buf == buf + + ret = gkfs_client.rename(filea, filec) + + #filea should be gone + assert ret.retval == -1 + + ret = gkfs_client.rename(fileb, filec) + assert ret.retval == 0 + + ret = gkfs_client.read(filec, len(buf)) + assert ret.retval == len(buf) + assert ret.buf == buf + + ret = gkfs_client.rename(filec, filed) + assert ret.retval == 0 + + ret = gkfs_client.read(filed, len(buf)) + assert ret.retval == len(buf) + assert ret.buf == buf + + + ret = gkfs_client.rename(filed, filee) + assert ret.retval == 0 + + ret = gkfs_client.read(filee, len(buf)) + assert ret.retval == len(buf) + assert ret.buf == buf + + + # check the stat of old files + ret = gkfs_client.stat(fileb) + assert ret.retval == -1 + + ret = gkfs_client.stat(filec) + assert ret.retval == -1 + + ret = gkfs_client.stat(filed) + assert ret.retval == -1 + + ret = gkfs_client.stat(filee) + assert ret.retval == 0 + +def test_cyclic_rename(gkfs_daemon, gkfs_client): + + fileold = gkfs_daemon.mountdir / "fileold" + filenew = gkfs_daemon.mountdir / "filenew" + + + # create a file in gekkofs + ret = gkfs_client.open(fileold, + os.O_CREAT | os.O_WRONLY, + stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) + + assert ret.retval == 10000 + + # write a buffer we know + buf = b'42' + ret = gkfs_client.write(fileold, buf, len(buf)) + + assert ret.retval == len(buf) # Return the number of written bytes + + ret = gkfs_client.stat(fileold) + assert ret.retval != 1 + + ret = gkfs_client.stat(filenew) + assert ret.retval == -1 + + ret = gkfs_client.rename(fileold, filenew) + + assert ret.retval == 0 + + ret = gkfs_client.stat(fileold) + assert ret.retval == -1 + + ret = gkfs_client.stat(filenew) + assert ret.retval != 1 + + + #Cyclic rename + ret = gkfs_client.rename(filenew, fileold) + + ret = gkfs_client.stat(fileold) + assert ret.retval != -1 + + ret = gkfs_client.stat(filenew) + assert ret.retval == -1 + # Read contents of filee + ret = gkfs_client.open(fileold, os.O_RDONLY) + assert ret.retval == 10000 + + ret = gkfs_client.read(fileold, len(buf)) + assert ret.retval == len(buf) + assert ret.buf == buf + +def test_rename_plus_trunc(gkfs_daemon, gkfs_client): + + fileold = gkfs_daemon.mountdir / "fileoldtr" + filenew = gkfs_daemon.mountdir / "filenewtr" + + + # create a file in gekkofs + ret = gkfs_client.open(fileold, + os.O_CREAT | os.O_WRONLY, + stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) + + assert ret.retval == 10000 + + # write a buffer we know + buf = b'42' + ret = gkfs_client.write(fileold, buf, len(buf)) + + assert ret.retval == len(buf) # Return the number of written bytes + + # rename file + ret = gkfs_client.rename(fileold, filenew) + assert ret.retval == 0 + + ret = gkfs_client.stat(filenew) + assert ret.retval != -1 + assert ret.statbuf.st_size == len(buf) + + # truncate file + ret = gkfs_client.truncate(filenew, 1) + assert ret.retval == 0 + + ret = gkfs_client.read(filenew, len(buf)) + assert ret.retval == 1 + assert ret.buf == b'4\x00' # buf includes the null byte + + ret = gkfs_client.stat(filenew) + assert ret.retval != -1 + assert ret.statbuf.st_size == 1 + +def test_rename_plus_lseek(gkfs_daemon, gkfs_client): + + fileold = gkfs_daemon.mountdir / "fileoldlseek" + filenew = gkfs_daemon.mountdir / "filenewlseek" + + + # create a file in gekkofs + ret = gkfs_client.open(fileold, + os.O_CREAT | os.O_WRONLY, + stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) + + assert ret.retval == 10000 + + # write a buffer we know + buf = b'42' + ret = gkfs_client.write(fileold, buf, len(buf)) + + assert ret.retval == len(buf) # Return the number of written bytes + + # rename file + ret = gkfs_client.rename(fileold, filenew) + assert ret.retval == 0 + + ret = gkfs_client.stat(filenew) + assert ret.retval != -1 + assert ret.statbuf.st_size == len(buf) + + ret = gkfs_client.lseek(filenew, 0, os.SEEK_END) + assert ret.retval == 2 #Two bytes written + + + +def test_rename_delete(gkfs_daemon, gkfs_client): + + fileold = gkfs_daemon.mountdir / "fileoldrename" + filenew = gkfs_daemon.mountdir / "filenewrename" + + + # create a file in gekkofs + ret = gkfs_client.open(fileold, + os.O_CREAT | os.O_WRONLY, + stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) + + assert ret.retval == 10000 + + # write a buffer we know + buf = b'42' + ret = gkfs_client.write(fileold, buf, len(buf)) + assert ret.retval == len(buf) # Return the number of written bytes + + # rename file + ret = gkfs_client.rename(fileold, filenew) + assert ret.retval == 0 + + ret = gkfs_client.stat(filenew) + assert ret.retval != -1 + assert ret.statbuf.st_size == len(buf) + + ret = gkfs_client.readdir(gkfs_daemon.mountdir) + assert len(ret.dirents) == 1 + + ret = gkfs_client.unlink(fileold) # Remove original file (error) + assert ret.retval != 0 + + ret = gkfs_client.unlink(filenew) # Remove renamed file (success) + assert ret.retval == 0 + + ret = gkfs_client.stat(filenew) + assert ret.retval == -1 + + ret = gkfs_client.readdir(gkfs_daemon.mountdir) + assert len(ret.dirents) == 0 + + +