/* 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' POSIX interface. GekkoFS' POSIX interface is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GekkoFS' POSIX interface 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with GekkoFS' POSIX interface. If not, see . SPDX-License-Identifier: LGPL-3.0-or-later */ #include #include #include #include #include #include #include #include #include extern "C" { #include // used for file types in the getdents{,64}() functions #include // used for definition of alignment macros #include #include } using namespace std; /* * Macro used within getdents{,64} functions. * __ALIGN_KERNEL defined in linux/kernel.h */ #define ALIGN(x, a) __ALIGN_KERNEL((x), (a)) /* * linux_dirent is used in getdents() but is privately defined in the linux * kernel: fs/readdir.c. */ struct linux_dirent { unsigned long d_ino; unsigned long d_off; unsigned short d_reclen; char d_name[1]; }; /* * linux_dirent64 is used in getdents64() and defined in the linux kernel: * include/linux/dirent.h. However, it is not part of the kernel-headers and * cannot be imported. */ struct linux_dirent64 { uint64_t d_ino; int64_t d_off; unsigned short d_reclen; unsigned char d_type; char d_name[1]; // originally `char d_name[0]` in kernel, but ISO C++ // forbids zero-size array 'd_name' }; struct dirent_extended { size_t size; time_t ctime; unsigned short d_reclen; unsigned char d_type; char d_name[1]; // originally `char d_name[0]` in kernel, but ISO C++ // forbids zero-size array 'd_name' }; namespace { /** * Checks if metadata for parent directory exists (can be disabled with * CREATE_CHECK_PARENTS). errno may be set * @param path * @return 0 on success, -1 on failure */ int check_parent_dir(const std::string& path) { #if CREATE_CHECK_PARENTS auto p_comp = gkfs::path::dirname(path); auto md = gkfs::utils::get_metadata(p_comp); if(!md) { if(errno == ENOENT) { LOG(DEBUG, "Parent component does not exist: '{}'", p_comp); } else { LOG(ERROR, "Failed to get metadata for parent component '{}': {}", path, strerror(errno)); } return -1; } if(!S_ISDIR(md->mode())) { LOG(DEBUG, "Parent component is not a directory: '{}'", p_comp); errno = ENOTDIR; return -1; } #endif // CREATE_CHECK_PARENTS return 0; } } // namespace namespace gkfs::syscall { /** * gkfs wrapper for open() system calls * errno may be set * @param path * @param mode * @param flags * @return 0 on success, -1 on failure */ int gkfs_open(const std::string& path, mode_t mode, int flags) { if(flags & O_PATH) { LOG(ERROR, "`O_PATH` flag is not supported"); errno = ENOTSUP; return -1; } if(flags & O_APPEND) { LOG(ERROR, "`O_APPEND` flag is not supported"); errno = ENOTSUP; return -1; } // metadata object filled during create or stat gkfs::metadata::Metadata md{}; if(flags & O_CREAT) { if(flags & O_DIRECTORY) { LOG(ERROR, "O_DIRECTORY use with O_CREAT. NOT SUPPORTED"); errno = ENOTSUP; return -1; } // no access check required here. If one is using our FS they have the // permissions. auto err = gkfs_create(path, mode | S_IFREG); if(err) { if(errno == EEXIST) { // file exists, O_CREAT was set if(flags & O_EXCL) { // File exists and O_EXCL & O_CREAT was set return -1; } // file exists, O_CREAT was set O_EXCL wasnt, so function does // not fail this case is actually undefined as per `man 2 open` auto md_ = gkfs::utils::get_metadata(path); if(!md_) { LOG(ERROR, "Could not get metadata after creating file '{}': '{}'", path, strerror(errno)); return -1; } md = *md_; } else { LOG(ERROR, "Error creating file: '{}'", strerror(errno)); return -1; } } else { // file was successfully created. Add to filemap return CTX->file_map()->add( std::make_shared(path, flags)); } } else { auto md_ = gkfs::utils::get_metadata(path); if(!md_) { if(errno != ENOENT) { LOG(ERROR, "Error stating existing file '{}'", path); } // file doesn't exists and O_CREAT was not set return -1; } md = *md_; } #ifdef HAS_SYMLINKS if(md.is_link()) { if(flags & O_NOFOLLOW) { LOG(WARNING, "Symlink found and O_NOFOLLOW flag was specified"); errno = ELOOP; return -1; } return gkfs_open(md.target_path(), mode, flags); } #endif if(S_ISDIR(md.mode())) { return gkfs_opendir(path); } /*** Regular file exists ***/ assert(S_ISREG(md.mode())); if((flags & O_TRUNC) && ((flags & O_RDWR) || (flags & O_WRONLY))) { if(gkfs_truncate(path, md.size(), 0)) { LOG(ERROR, "Error truncating file"); return -1; } } return CTX->file_map()->add( std::make_shared(path, flags)); } /** * Wrapper function for file/directory creation * errno may be set * @param path * @param mode * @return 0 on success, -1 on failure */ int gkfs_create(const std::string& path, mode_t mode) { // file type must be set 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: LOG(WARNING, "Unsupported node type"); errno = ENOTSUP; return -1; default: LOG(WARNING, "Unrecognized node type"); errno = EINVAL; return -1; } if(check_parent_dir(path)) { return -1; } auto err = gkfs::rpc::forward_create(path, mode); if(err) { errno = err; return -1; } return 0; } /** * gkfs wrapper for unlink() system calls * errno may be set * @param path * @return 0 on success, -1 on failure */ int gkfs_remove(const std::string& path) { auto md = gkfs::utils::get_metadata(path); if(!md) { return -1; } if(S_ISDIR(md->mode())) { LOG(ERROR, "Cannot remove directory '{}'", path); errno = EISDIR; return -1; } auto err = gkfs::rpc::forward_remove(path); if(err) { errno = err; return -1; } return 0; } /** * gkfs wrapper for access() system calls * errno may be set * @param path * @param mask * @param follow_links * @return 0 on success, -1 on failure */ int gkfs_access(const std::string& path, const int mask, bool follow_links) { auto md = gkfs::utils::get_metadata(path, follow_links); if(!md) { return -1; } return 0; } /** * gkfs wrapper for stat() system calls * errno may be set * @param path * @param buf * @param follow_links * @return 0 on success, -1 on failure */ int gkfs_stat(const string& path, struct stat* buf, bool follow_links) { auto md = gkfs::utils::get_metadata(path, follow_links); if(!md) { return -1; } gkfs::utils::metadata_to_stat(path, *md, *buf); return 0; } #ifdef STATX_TYPE /** * gkfs wrapper for statx() system calls * errno may be set * @param dirfs * @param path * @param flags * @param mask * @param buf * @param follow_links * @return 0 on success, -1 on failure */ 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; } struct stat tmp {}; gkfs::utils::metadata_to_stat(path, *md, tmp); buf->stx_mask = 0; buf->stx_blksize = tmp.st_blksize; buf->stx_attributes = 0; buf->stx_nlink = tmp.st_nlink; buf->stx_uid = tmp.st_uid; buf->stx_gid = tmp.st_gid; buf->stx_mode = tmp.st_mode; buf->stx_ino = tmp.st_ino; buf->stx_size = tmp.st_size; buf->stx_blocks = tmp.st_blocks; buf->stx_attributes_mask = 0; buf->stx_atime.tv_sec = tmp.st_atim.tv_sec; buf->stx_atime.tv_nsec = tmp.st_atim.tv_nsec; buf->stx_mtime.tv_sec = tmp.st_mtim.tv_sec; buf->stx_mtime.tv_nsec = tmp.st_mtim.tv_nsec; buf->stx_ctime.tv_sec = tmp.st_ctim.tv_sec; buf->stx_ctime.tv_nsec = tmp.st_ctim.tv_nsec; buf->stx_btime = buf->stx_atime; return 0; } #endif /** * gkfs wrapper for statfs() system calls * errno may be set * @param buf * @return 0 on success, -1 on failure */ int gkfs_statfs(struct statfs* buf) { auto ret = gkfs::rpc::forward_get_chunk_stat(); auto err = ret.first; if(err) { LOG(ERROR, "{}() Failure with error: '{}'", err); errno = err; return -1; } auto blk_stat = ret.second; buf->f_type = 0; buf->f_bsize = blk_stat.chunk_size; buf->f_blocks = blk_stat.chunk_total; buf->f_bfree = blk_stat.chunk_free; buf->f_bavail = blk_stat.chunk_free; buf->f_files = 0; buf->f_ffree = 0; buf->f_fsid = {0, 0}; buf->f_namelen = path::max_length; buf->f_frsize = 0; buf->f_flags = ST_NOATIME | ST_NODIRATIME | ST_NOSUID | ST_NODEV | ST_SYNCHRONOUS; return 0; } #ifdef GKFS_ENABLE_UNUSED_FUNCTIONS /** * gkfs wrapper for statvfs() system calls * errno may be set * * NOTE: Currently unused. * * @param buf * @return 0 on success, -1 on failure */ int gkfs_statvfs(struct statvfs* buf) { auto ret = gkfs::rpc::forward_get_chunk_stat(); auto err = ret.first; if(err) { LOG(ERROR, "{}() Failure with error: '{}'", err); errno = err; return -1; } auto blk_stat = ret.second; buf->f_bsize = blk_stat.chunk_size; buf->f_blocks = blk_stat.chunk_total; buf->f_bfree = blk_stat.chunk_free; buf->f_bavail = blk_stat.chunk_free; buf->f_files = 0; buf->f_ffree = 0; buf->f_favail = 0; buf->f_fsid = 0; buf->f_namemax = path::max_length; buf->f_frsize = 0; buf->f_flag = ST_NOATIME | ST_NODIRATIME | ST_NOSUID | ST_NODEV | ST_SYNCHRONOUS; return 0; } #endif /** * gkfs wrapper for lseek() system calls with available file descriptor * errno may be set * @param fd * @param offset * @param whence * @return 0 on success, -1 on failure */ off_t gkfs_lseek(unsigned int fd, off_t offset, unsigned int whence) { return gkfs_lseek(CTX->file_map()->get(fd), offset, whence); } /** * gkfs wrapper for lseek() system calls with available shared ptr to gkfs * FileMap errno may be set * @param gkfs_fd * @param offset * @param whence * @return 0 on success, -1 on failure */ off_t gkfs_lseek(shared_ptr gkfs_fd, off_t offset, unsigned int whence) { switch(whence) { case SEEK_SET: if(offset < 0) { errno = EINVAL; return -1; } gkfs_fd->pos(offset); break; case SEEK_CUR: gkfs_fd->pos(gkfs_fd->pos() + offset); break; case SEEK_END: { auto ret = gkfs::rpc::forward_get_metadentry_size(gkfs_fd->path()); auto err = ret.first; if(err) { errno = err; return -1; } auto file_size = ret.second; if(offset < 0 && file_size < -offset) { errno = EINVAL; return -1; } gkfs_fd->pos(file_size + offset); break; } case SEEK_DATA: LOG(WARNING, "SEEK_DATA whence is not supported"); // We do not support this whence yet errno = EINVAL; return -1; case SEEK_HOLE: LOG(WARNING, "SEEK_HOLE whence is not supported"); // We do not support this whence yet errno = EINVAL; return -1; default: LOG(WARNING, "Unknown whence value {:#x}", whence); errno = EINVAL; return -1; } return gkfs_fd->pos(); } /** * wrapper function for gkfs_truncate * errno may be set * @param path * @param old_size * @param new_size * @return 0 on success, -1 on failure */ int gkfs_truncate(const std::string& path, off_t old_size, off_t new_size) { assert(new_size >= 0); assert(new_size <= old_size); if(new_size == old_size) { return 0; } auto err = gkfs::rpc::forward_decr_size(path, new_size); if(err) { LOG(DEBUG, "Failed to decrease size"); errno = err; return -1; } err = gkfs::rpc::forward_truncate(path, old_size, new_size); if(err) { LOG(DEBUG, "Failed to truncate data"); errno = err; return -1; } return 0; } /** * gkfs wrapper for truncate() system calls * errno may be set * @param path * @param length * @return 0 on success, -1 on failure */ int gkfs_truncate(const std::string& path, off_t length) { /* TODO CONCURRENCY: * At the moment we first ask the length to the metadata-server in order to * know which data-server have data to be deleted. * * From the moment we issue the gkfs_stat and the moment we issue the * gkfs_trunc_data, some more data could have been added to the file and the * length increased. */ if(length < 0) { LOG(DEBUG, "Length is negative: {}", length); errno = EINVAL; return -1; } auto md = gkfs::utils::get_metadata(path, true); if(!md) { return -1; } 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(path, size, length); } /** * gkfs wrapper for dup() system calls * errno may be set * @param oldfd * @return file descriptor int or -1 on error */ int gkfs_dup(const int oldfd) { return CTX->file_map()->dup(oldfd); } /** * gkfs wrapper for dup2() system calls * errno may be set * @param oldfd * @param newfd * @return file descriptor int or -1 on error */ int gkfs_dup2(const int oldfd, const int newfd) { return CTX->file_map()->dup2(oldfd, newfd); } /** * Wrapper function for all gkfs write operations * errno may be set * @param file * @param buf * @param count * @param offset * @return written size or -1 on error */ ssize_t gkfs_pwrite(std::shared_ptr file, const char* buf, size_t count, off64_t offset) { if(file->type() != gkfs::filemap::FileType::regular) { assert(file->type() == gkfs::filemap::FileType::directory); LOG(WARNING, "Cannot read from directory"); errno = EISDIR; return -1; } auto path = make_shared(file->path()); auto append_flag = file->get_flag(gkfs::filemap::OpenFile_flags::append); auto ret_update_size = gkfs::rpc::forward_update_metadentry_size( *path, count, offset, append_flag); auto err = ret_update_size.first; if(err) { LOG(ERROR, "update_metadentry_size() failed with err '{}'", err); errno = err; return -1; } auto updated_size = ret_update_size.second; auto ret_write = gkfs::rpc::forward_write(*path, buf, append_flag, offset, count, updated_size); err = ret_write.first; if(err) { LOG(WARNING, "gkfs::rpc::forward_write() failed with err '{}'", err); errno = err; return -1; } return ret_write.second; // return written size } /** * gkfs wrapper for pwrite() system calls * errno may be set * @param fd * @param buf * @param count * @param offset * @return written size or -1 on error */ ssize_t gkfs_pwrite_ws(int fd, const void* buf, size_t count, off64_t offset) { auto file = CTX->file_map()->get(fd); return gkfs_pwrite(file, reinterpret_cast(buf), count, offset); } /** * gkfs wrapper for write() system calls * errno may be set * @param fd * @param buf * @param count * @return written size or -1 on error */ ssize_t gkfs_write(int fd, const void* buf, size_t count) { auto gkfs_fd = CTX->file_map()->get(fd); auto pos = gkfs_fd->pos(); // retrieve the current offset if(gkfs_fd->get_flag(gkfs::filemap::OpenFile_flags::append)) gkfs_lseek(gkfs_fd, 0, SEEK_END); auto ret = gkfs_pwrite(gkfs_fd, reinterpret_cast(buf), count, pos); // Update offset in file descriptor in the file map if(ret > 0) { gkfs_fd->pos(pos + count); } return ret; } /** * gkfs wrapper for pwritev() system calls * errno may be set * @param fd * @param iov * @param iovcnt * @param offset * @return written size or -1 on error */ ssize_t gkfs_pwritev(int fd, const struct iovec* iov, int iovcnt, off_t offset) { auto file = CTX->file_map()->get(fd); auto pos = offset; // keep track of current position ssize_t written = 0; ssize_t ret; for(int i = 0; i < iovcnt; ++i) { auto count = (iov + i)->iov_len; if(count == 0) { continue; } auto buf = (iov + i)->iov_base; ret = gkfs_pwrite(file, reinterpret_cast(buf), count, pos); if(ret == -1) { break; } written += ret; pos += ret; if(static_cast(ret) < count) { break; } } if(written == 0) { return -1; } return written; } /** * gkfs wrapper for writev() system calls * errno may be set * @param fd * @param iov * @param iovcnt * @return written size or -1 on error */ ssize_t gkfs_writev(int fd, const struct iovec* iov, int iovcnt) { auto gkfs_fd = CTX->file_map()->get(fd); auto pos = gkfs_fd->pos(); // retrieve the current offset auto ret = gkfs_pwritev(fd, iov, iovcnt, pos); assert(ret != 0); if(ret < 0) { return -1; } gkfs_fd->pos(pos + ret); return ret; } /** * Wrapper function for all gkfs read operations * @param file * @param buf * @param count * @param offset * @return read size or -1 on error */ ssize_t gkfs_pread(std::shared_ptr file, char* buf, size_t count, off64_t offset) { if(file->type() != gkfs::filemap::FileType::regular) { assert(file->type() == gkfs::filemap::FileType::directory); LOG(WARNING, "Cannot read from directory"); errno = EISDIR; return -1; } // Zeroing buffer before read is only relevant for sparse files. Otherwise // sparse regions contain invalid data. if constexpr(gkfs::config::io::zero_buffer_before_read) { memset(buf, 0, sizeof(char) * count); } auto ret = gkfs::rpc::forward_read(file->path(), buf, offset, count); auto err = ret.first; if(err) { LOG(WARNING, "gkfs::rpc::forward_read() failed with ret '{}'", err); errno = err; return -1; } // XXX check that we don't try to read past end of the file return ret.second; // return read size } /** * gkfs wrapper for read() system calls * errno may be set * @param fd * @param buf * @param count * @return read size or -1 on error */ ssize_t gkfs_read(int fd, void* buf, size_t count) { auto gkfs_fd = CTX->file_map()->get(fd); auto pos = gkfs_fd->pos(); // retrieve the current offset auto ret = gkfs_pread(gkfs_fd, reinterpret_cast(buf), count, pos); // Update offset in file descriptor in the file map if(ret > 0) { gkfs_fd->pos(pos + ret); } return ret; } /** * gkfs wrapper for preadv() system calls * errno may be set * @param fd * @param iov * @param iovcnt * @param offset * @return read size or -1 on error */ ssize_t gkfs_preadv(int fd, const struct iovec* iov, int iovcnt, off_t offset) { auto file = CTX->file_map()->get(fd); auto pos = offset; // keep track of current position ssize_t read = 0; ssize_t ret; for(int i = 0; i < iovcnt; ++i) { auto count = (iov + i)->iov_len; if(count == 0) { continue; } auto buf = (iov + i)->iov_base; ret = gkfs_pread(file, reinterpret_cast(buf), count, pos); if(ret == -1) { break; } read += ret; pos += ret; if(static_cast(ret) < count) { break; } } if(read == 0) { return -1; } return read; } /** * gkfs wrapper for readv() system calls * errno may be set * @param fd * @param iov * @param iovcnt * @return read size or -1 on error */ ssize_t gkfs_readv(int fd, const struct iovec* iov, int iovcnt) { auto gkfs_fd = CTX->file_map()->get(fd); auto pos = gkfs_fd->pos(); // retrieve the current offset auto ret = gkfs_preadv(fd, iov, iovcnt, pos); assert(ret != 0); if(ret < 0) { return -1; } gkfs_fd->pos(pos + ret); return ret; } /** * gkfs wrapper for pread() system calls * errno may be set * @param fd * @param buf * @param count * @param offset * @return read size or -1 on error */ ssize_t gkfs_pread_ws(int fd, void* buf, size_t count, off64_t offset) { auto gkfs_fd = CTX->file_map()->get(fd); return gkfs_pread(gkfs_fd, reinterpret_cast(buf), count, offset); } /** * wrapper function for opening directories * errno may be set * @param path * @return 0 on success or -1 on error */ int gkfs_opendir(const std::string& path) { auto md = gkfs::utils::get_metadata(path); if(!md) { return -1; } if(!S_ISDIR(md->mode())) { LOG(DEBUG, "Path is not a directory"); errno = ENOTDIR; return -1; } auto ret = gkfs::rpc::forward_get_dirents(path); auto err = ret.first; if(err) { errno = err; return -1; } assert(ret.second); return CTX->file_map()->add(ret.second); } /** * gkfs wrapper for rmdir() system calls * errno may be set * @param path * @return 0 on success or -1 on error */ int gkfs_rmdir(const std::string& path) { auto md = gkfs::utils::get_metadata(path); if(!md) { LOG(DEBUG, "Error: Path '{}' err code '{}' ", path, strerror(errno)); return -1; } if(!S_ISDIR(md->mode())) { LOG(DEBUG, "Path '{}' is not a directory", path); errno = ENOTDIR; return -1; } auto ret = gkfs::rpc::forward_get_dirents(path); auto err = ret.first; if(err) { errno = err; return -1; } assert(ret.second); auto open_dir = ret.second; if(open_dir->size() != 0) { errno = ENOTEMPTY; return -1; } err = gkfs::rpc::forward_remove(path); if(err) { errno = err; return -1; } return 0; } /** * gkfs wrapper for getdents() system calls * errno may be set * @param fd * @param dirp * @param count * @return 0 on success or -1 on error */ int gkfs_getdents(unsigned int fd, struct linux_dirent* dirp, unsigned int count) { // Get opendir object (content was downloaded with opendir() call) auto open_dir = CTX->file_map()->get_dir(fd); if(open_dir == nullptr) { // Cast did not succeeded: open_file is a regular file errno = EBADF; return -1; } // get directory position of which entries to return auto pos = open_dir->pos(); if(pos >= open_dir->size()) { return 0; } unsigned int written = 0; struct linux_dirent* current_dirp = nullptr; while(pos < open_dir->size()) { // get dentry fir current position auto de = open_dir->getdent(pos); /* * Calculate the total dentry size within the kernel struct * `linux_dirent` depending on the file name size. The size is then * aligned to the size of `long` boundary. This line was originally * defined in the linux kernel: fs/readdir.c in function filldir(): int * reclen = ALIGN(offsetof(struct linux_dirent, d_name) + namlen + 2, * sizeof(long)); However, since d_name is null-terminated and * de.name().size() does not include space for the null-terminator, we * add 1. Thus, + 3 in total. */ auto total_size = ALIGN(offsetof(struct linux_dirent, d_name) + de.name().size() + 3, sizeof(long)); if(total_size > (count - written)) { // no enough space left on user buffer to insert next dirent break; } current_dirp = reinterpret_cast( reinterpret_cast(dirp) + written); current_dirp->d_ino = std::hash()(open_dir->path() + "/" + de.name()); current_dirp->d_reclen = total_size; *(reinterpret_cast(current_dirp) + total_size - 1) = ((de.type() == gkfs::filemap::FileType::regular) ? DT_REG : DT_DIR); LOG(DEBUG, "name {}: {}", pos, de.name()); std::strcpy(&(current_dirp->d_name[0]), de.name().c_str()); ++pos; current_dirp->d_off = pos; written += total_size; } if(written == 0) { errno = EINVAL; return -1; } // set directory position for next getdents() call open_dir->pos(pos); return written; } /** * gkfs wrapper for getdents64() system calls * errno may be set * @param fd * @param dirp * @param count * @return 0 on success or -1 on error */ int gkfs_getdents64(unsigned int fd, struct linux_dirent64* dirp, unsigned int count) { auto open_dir = CTX->file_map()->get_dir(fd); if(open_dir == nullptr) { // Cast did not succeeded: open_file is a regular file errno = EBADF; return -1; } auto pos = open_dir->pos(); if(pos >= open_dir->size()) { return 0; } unsigned int written = 0; struct linux_dirent64* current_dirp = nullptr; while(pos < open_dir->size()) { auto de = open_dir->getdent(pos); /* * Calculate the total dentry size within the kernel struct * `linux_dirent` depending on the file name size. The size is then * aligned to the size of `long` boundary. * * This line was originally defined in the linux kernel: fs/readdir.c in * function filldir64(): int reclen = ALIGN(offsetof(struct * linux_dirent64, d_name) + namlen + 1, sizeof(u64)); We keep + 1 * because: Since d_name is null-terminated and de.name().size() does * not include space for the null-terminator, we add 1. Since d_name in * our `struct linux_dirent64` definition is not a zero-size array (as * opposed to the kernel version), we subtract 1. Thus, it stays + 1. */ auto total_size = ALIGN(offsetof(struct linux_dirent64, d_name) + de.name().size() + 1, sizeof(uint64_t)); if(total_size > (count - written)) { // no enough space left on user buffer to insert next dirent break; } current_dirp = reinterpret_cast( reinterpret_cast(dirp) + written); current_dirp->d_ino = std::hash()(open_dir->path() + "/" + de.name()); current_dirp->d_reclen = total_size; current_dirp->d_type = ((de.type() == gkfs::filemap::FileType::regular) ? DT_REG : DT_DIR); LOG(DEBUG, "name {}: {}", pos, de.name()); std::strcpy(&(current_dirp->d_name[0]), de.name().c_str()); ++pos; current_dirp->d_off = pos; written += total_size; } if(written == 0) { errno = EINVAL; return -1; } open_dir->pos(pos); return written; } #ifdef HAS_SYMLINKS #ifdef GKFS_ENABLE_UNUSED_FUNCTIONS /** * gkfs wrapper for make symlink() system calls * errno may be set * * * NOTE: Currently unused * * @param path * @param target_path * @return 0 on success or -1 on error */ 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. */ auto target_md = gkfs::utils::get_metadata(target_path, false); 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; } } if(check_parent_dir(path)) { return -1; } auto link_md = gkfs::utils::get_metadata(path, false); if(link_md) { LOG(DEBUG, "Link exists: '{}'", path); errno = EEXIST; return -1; } auto err = gkfs::rpc::forward_mk_symlink(path, target_path); if(err) { errno = err; return -1; } return 0; } /** * gkfs wrapper for reading symlinks * errno may be set * * NOTE: Currently unused * * @param path * @param buf * @param bufsize * @return 0 on success or -1 on error */ int gkfs_readlink(const std::string& path, char* buf, int bufsize) { auto md = gkfs::utils::get_metadata(path, false); if(!md) { LOG(DEBUG, "Named link doesn't exist"); return -1; } if(!(md->is_link())) { LOG(DEBUG, "The named file is not a symbolic link"); errno = EINVAL; return -1; } int path_size = md->target_path().size() + CTX->mountdir().size(); if(path_size >= bufsize) { LOG(WARNING, "Destination buffer size is too short: {} < {}, {} ", 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 #endif } // namespace gkfs::syscall /* This function defines an extension of the dirents prepared to do a find-like * function The function only sends the request to the specified server */ extern "C" int gkfs_getsingleserverdir(const char* path, struct dirent_extended* dirp, unsigned int count, int server) { auto ret = gkfs::rpc::forward_get_dirents_single(path, server); auto err = ret.first; if(err) { errno = err; return -1; } auto open_dir = ret.second; unsigned int pos = 0; unsigned int written = 0; struct dirent_extended* current_dirp = nullptr; while(pos < open_dir.size()) { auto de = open_dir[pos]; /* * Calculate the total dentry size within the 'dirent_extended` * depending on the file name size. The size is then aligned to the size * of `long` boundary. */ auto total_size = ALIGN(offsetof(struct dirent_extended, d_name) + (get<0>(de)).size() + 1, sizeof(uint64_t)); if(total_size > (count - written)) { // no enough space left on user buffer to insert next dirent break; } current_dirp = reinterpret_cast( reinterpret_cast(dirp) + written); current_dirp->d_reclen = total_size; current_dirp->d_type = get<1>(de); current_dirp->size = get<2>(de); current_dirp->ctime = get<3>(de); LOG(DEBUG, "name {}: {} {} {} {} / size {}", pos, get<0>(de), get<1>(de), get<2>(de), get<3>(de), total_size); std::strcpy(&(current_dirp->d_name[0]), (get<0>(de)).c_str()); ++pos; written += total_size; } if(written == 0) { errno = EINVAL; return -1; } return written; }