From f0ca658689027fba150cd109a37ba55ba7129b27 Mon Sep 17 00:00:00 2001 From: Andreas Kohn Date: Tue, 12 Mar 2024 10:11:35 +0100 Subject: [PATCH] WIP: Improve performance * Use more consistent typing on the C++ side: paths are fs::path, strings are std::string * Implement FUSE open/release and avoid opening/closing the file for each read call (as well as the expensive get_real_path!) * Implement FUS opendir/releasedir * Reduce the number of directories that need to be lstat-ed when using readdir by remembering the real path of the root. --- CMakeLists.txt | 4 +-- src/dir.cc | 71 ++++++++++++++++++++++++++++++++++++++++ src/{read.cc => file.cc} | 22 +++++++++---- src/get_real_path.cc | 52 ++++++++++++++++++++--------- src/getattr.cc | 11 +++++-- src/main.cc | 12 ++++--- src/readdir.cc | 40 ---------------------- src/tmfs.hh | 10 +++++- 8 files changed, 150 insertions(+), 72 deletions(-) create mode 100644 src/dir.cc rename src/{read.cc => file.cc} (50%) delete mode 100644 src/readdir.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 074077e..6d6be01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,8 +12,8 @@ include_directories(${Boost_INCLUDE_DIRS}) add_executable(tmfs src/main.cc - src/readdir.cc - src/read.cc + src/dir.cc + src/file.cc src/readlink.cc src/getattr.cc src/get_real_path.cc diff --git a/src/dir.cc b/src/dir.cc new file mode 100644 index 0000000..8021fa9 --- /dev/null +++ b/src/dir.cc @@ -0,0 +1,71 @@ +#include "tmfs.hh" + +typedef struct dirhandle { + DIR * dir; + fs::path real_path; +} dirhandle; + +int tmfs_opendir(const char * path, struct fuse_file_info * fi) +{ + // get the real path + fs::path real_path = get_real_path(path); + + // checks if it's really a directory + if (!fs::is_directory(real_path)) + return -ENOTDIR; + + dirhandle * dh = new dirhandle; + dh->dir = opendir(real_path.c_str()); + if (dh->dir == 0) + return -errno; + + dh->real_path = real_path; + fi->fh = (intptr_t)dh; + + return 0; +} + +int tmfs_releasedir(const char * path, struct fuse_file_info * fi) +{ + dirhandle * dh = (dirhandle *)fi->fh; + int res = closedir(dh->dir); + delete dh; + return res; +} + +int tmfs_readdir(const char * path, void * buf, fuse_fill_dir_t filler_cb, off_t offset, + struct fuse_file_info * fi) +{ + struct stat stbuf; + + dirhandle * dh = (dirhandle *)fi->fh; + + // XXX: Midnight Commander seems to be doing something weird, and calls readdir twice + // on the same directory. As we report everything in one go this means the second call + // produces an empty output. + // See also https://github.com/littlefs-project/littlefs-fuse/issues/43 + // This is a workaround for that, and it's obviously quite expensive for big directories ... + rewinddir(((dirhandle *)fi->fh)->dir); + + // report ./ and ../ + stbuf.st_mode = S_IFDIR | 0755; + stbuf.st_nlink = 2; + filler_cb(buf, ".", &stbuf, 0); + filler_cb(buf, "..", &stbuf, 0); + + struct dirent * entry; + while ((entry = readdir(dh->dir))) + { + // Skip '.' and '..', we already reported those. + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + // stat the file pointed by entry + if (getattr_at(dh->real_path, entry->d_name, &stbuf)) + continue; + stbuf.st_mode |= 0755; + // report the entry + filler_cb(buf, entry->d_name, &stbuf, 0); + } + return 0; +} diff --git a/src/read.cc b/src/file.cc similarity index 50% rename from src/read.cc rename to src/file.cc index 5cf07f6..d797619 100644 --- a/src/read.cc +++ b/src/file.cc @@ -1,19 +1,29 @@ #include "tmfs.hh" -int tmfs_read(const char * path, char * buf, size_t nbytes, off_t offset, - struct fuse_file_info * fi) +int tmfs_open(const char * path, struct fuse_file_info * fi) { // get the real path std::string real_path = get_real_path(path); // open the file - int fd = open(real_path.c_str(), O_RDONLY); + int fd = open(real_path.c_str(), fi->flags); if (fd < 0) return -errno; + fi->fh = fd; + return 0; +} - // read the data and close - ssize_t bytes = pread(fd, buf, nbytes, offset); - close(fd); +int tmfs_release(const char * path, struct fuse_file_info * fi) +{ + close(fi->fh); + return 0; +} + +int tmfs_read(const char * path, char * buf, size_t nbytes, off_t offset, + struct fuse_file_info * fi) +{ + // read the data + ssize_t bytes = pread(fi->fh, buf, nbytes, offset); if (bytes < 0) return -errno; return bytes; diff --git a/src/get_real_path.cc b/src/get_real_path.cc index 2ad46c9..919e1a7 100644 --- a/src/get_real_path.cc +++ b/src/get_real_path.cc @@ -1,18 +1,29 @@ #include "tmfs.hh" -static std::string _get_real_path(const std::string & str) +static fs::path _get_real_path(const fs::path & known_real_path, const std::string & relative_path) { // use the relative path so that the real_path doesn't get replaced - const auto clean_path = fs::path(str).relative_path(); + const auto clean_path = fs::path(relative_path).relative_path(); + auto it = clean_path.begin(); - fs::path real_path(tmfs::instance().hfs_root()); - real_path /= "Backups.backupdb"; // ${hfs_root}/Backups.backupdb/ + fs::path real_path; + if (known_real_path.empty()) { + // Start from the root of the HFS + real_path = fs::path(tmfs::instance().hfs_root()); + real_path /= "Backups.backupdb"; // ${hfs_root}/Backups.backupdb/ - // ok let's copy the 3 first part of the virtual path - // (${comp_name}, ${date}, ${disk_name}) - auto it = clean_path.begin(); - for (int i = 0; i < 3 && it != clean_path.end(); ++i, ++it) - real_path /= *it; + // ok let's copy the 3 first part of the virtual path + // (${comp_name}, ${date}, ${disk_name}) + for (int i = 0; i < 3 && it != clean_path.end(); ++i, ++it) + real_path /= *it; + } else { + // Start from the known real path, which should already contain the reference to the correct + // Backups.backupdb sub-directory in the HFS root. + real_path = known_real_path; + } + + fs::path private_data_dir = tmfs::instance().hfs_root() / ".HFS+ Private Directory Data\r"; + std::string dir_name_prefix = "dir_"; // let's resolv all the parts of the path struct stat stbuf; @@ -21,14 +32,14 @@ static std::string _get_real_path(const std::string & str) real_path /= *it; // Does the file exists ? if (lstat(real_path.string().c_str(), &stbuf)) - return real_path.string(); + return real_path; // Is the file a dir_id ? if (S_ISREG(stbuf.st_mode) && stbuf.st_size == 0 && stbuf.st_nlink > 0) { // build the real path - fs::path dir_path = tmfs::instance().hfs_root(); - dir_path /= ".HFS+ Private Directory Data\r/dir_" + std::to_string(stbuf.st_nlink); + std::string dir_name = dir_name_prefix + std::to_string(stbuf.st_nlink); + fs::path dir_path = private_data_dir / dir_name; // check if it's really a ${dir_id} if (stat(dir_path.c_str(), &stbuf)) @@ -36,14 +47,23 @@ static std::string _get_real_path(const std::string & str) real_path = dir_path; // it is } } - return real_path.string(); + return real_path; } -std::string get_real_path(const std::string & str) +fs::path get_real_path(const std::string & str) { - auto result = _get_real_path(str); + auto result = _get_real_path("", str); #ifndef NDEBUG - std::cout << "get_real_path(\"" << str << "\") -> " << result << std::endl; + std::cout << "get_real_path(\"" << str << "\") -> " << result << std::endl; #endif return result; } + +fs::path get_real_path_at(const fs::path & known_real_path, const std::string & relative_path) +{ + auto result = _get_real_path(known_real_path, relative_path); +#ifndef NDEBUG + std::cout << "get_real_path_at(" << known_real_path << ", \"" << relative_path << "\") -> " << result << std::endl; +#endif + return result; +} \ No newline at end of file diff --git a/src/getattr.cc b/src/getattr.cc index 636b936..b3eeb67 100644 --- a/src/getattr.cc +++ b/src/getattr.cc @@ -1,13 +1,18 @@ #include "tmfs.hh" int tmfs_getattr(const char *path, struct stat *stbuf) +{ + return getattr_at("", path, stbuf); +} + +int getattr_at(const fs::path & known_real_path, const std::string & relative_path, struct stat *stbuf) { // get the real path - std::string real_path = get_real_path(path); + fs::path real_path = get_real_path_at(known_real_path, relative_path); // and now just stat the real path memset(stbuf, 0, sizeof(struct stat)); - if (lstat(real_path.c_str(), stbuf)) + if (lstat(real_path.string().c_str(), stbuf)) return -errno; return 0; -} +} \ No newline at end of file diff --git a/src/main.cc b/src/main.cc index c426a5e..62ed826 100644 --- a/src/main.cc +++ b/src/main.cc @@ -30,10 +30,14 @@ int main(int argc, char ** argv) /* vtable setup */ struct fuse_operations ops; memset(&ops, 0, sizeof (ops)); - ops.read = tmfs_read; - ops.getattr = tmfs_getattr; - ops.readdir = tmfs_readdir; - ops.readlink = tmfs_readlink; + ops.open = tmfs_open; + ops.release = tmfs_release; + ops.read = tmfs_read; + ops.getattr = tmfs_getattr; + ops.opendir = tmfs_opendir; + ops.readdir = tmfs_readdir; + ops.releasedir = tmfs_releasedir; + ops.readlink = tmfs_readlink; /* lets go */ fuse_main(argc, argv, &ops, NULL); diff --git a/src/readdir.cc b/src/readdir.cc deleted file mode 100644 index 9961b74..0000000 --- a/src/readdir.cc +++ /dev/null @@ -1,40 +0,0 @@ -#include "tmfs.hh" - -int tmfs_readdir(const char * path, void * buf, fuse_fill_dir_t filler_cb, off_t offset, - struct fuse_file_info * fi) -{ - // get the real path - std::string real_path = get_real_path(path); - - // checks if it's really a directory - if (!fs::is_directory(real_path)) - return -ENOTDIR; - - struct stat stbuf; - - // report ./ and ../ - stbuf.st_mode = S_IFDIR | 0755; - stbuf.st_nlink = 2; - filler_cb(buf, ".", &stbuf, 0); - filler_cb(buf, "..", &stbuf, 0); - - // now iterate over the real directory - DIR * dir = opendir(real_path.c_str()); - if (!dir) - return 0; - - struct dirent * entry; - while ((entry = readdir(dir))) - { - // stat the file pointed by entry - auto file_path = fs::path(path) / entry->d_name; - if (tmfs_getattr(file_path.string().c_str(), &stbuf)) - continue; - stbuf.st_mode |= 0755; - // report the entry - filler_cb(buf, entry->d_name, &stbuf, 0); - } - closedir(dir); - - return 0; -} diff --git a/src/tmfs.hh b/src/tmfs.hh index 8c16b9f..3f6376d 100644 --- a/src/tmfs.hh +++ b/src/tmfs.hh @@ -34,14 +34,22 @@ struct tmfs { }; /** transforms a virtual paths in the tmfs's root to the real path in hfs's root */ -std::string get_real_path(const std::string & path); +fs::path get_real_path(const std::string & path); +fs::path get_real_path_at(const fs::path & known_real_path, const std::string & relative_path); + +/** get the attributes of a file relative to the known real path */ +int getattr_at(const fs::path & known_real_path, const std::string & relative_path, struct stat * stbuf); /** fuse functions * @{ */ int tmfs_getattr(const char * path, struct stat *stbuf); +int tmfs_opendir(const char * path, struct fuse_file_info * fi); int tmfs_readdir(const char * path, void * buf, fuse_fill_dir_t filler_callback, off_t offset, struct fuse_file_info * fi); +int tmfs_releasedir(const char * path, struct fuse_file_info * fi); int tmfs_read(const char * path, char * buf, size_t nbytes, off_t offset, struct fuse_file_info * fi); int tmfs_readlink(const char * path, char * buf, size_t size); +int tmfs_open(const char * path, struct fuse_file_info * fi); +int tmfs_release(const char * path, struct fuse_file_info * fi); /** @} */