/* * s3fs - FUSE-based file system backed by Amazon S3 * * Copyright(C) 2007 Takeshi Nakatani * * This program 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 2 * of the License, or (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "fdcache.h" #include "fdcache_stat.h" #include "s3fs_util.h" #include "s3fs_logger.h" #include "s3fs_cred.h" #include "string_util.h" #include "autolock.h" // // The following symbols are used by FdManager::RawCheckAllCache(). // // These must be #defines due to string literal concatenation. #define CACHEDBG_FMT_HEAD "---------------------------------------------------------------------------\n" \ "Check cache file and its stats file consistency at %s\n" \ "---------------------------------------------------------------------------" #define CACHEDBG_FMT_FOOT "---------------------------------------------------------------------------\n" \ "Summary - Total files: %d\n" \ " Detected error files: %d\n" \ " Detected error directories: %d\n" \ "---------------------------------------------------------------------------" #define CACHEDBG_FMT_FILE_OK "File: %s%s -> [OK] no problem" #define CACHEDBG_FMT_FILE_PROB "File: %s%s" #define CACHEDBG_FMT_DIR_PROB "Directory: %s" #define CACHEDBG_FMT_ERR_HEAD " -> [E] there is a mark that data exists in stats, but there is no data in the cache file." #define CACHEDBG_FMT_WARN_HEAD " -> [W] These show no data in stats, but there is evidence of data in the cache file(no problem)." #define CACHEDBG_FMT_WARN_OPEN "\n -> [W] This file is currently open and may not provide accurate analysis results." #define CACHEDBG_FMT_CRIT_HEAD " -> [C] %s" #define CACHEDBG_FMT_CRIT_HEAD2 " -> [C] " #define CACHEDBG_FMT_PROB_BLOCK " 0x%016zx(0x%016zx bytes)" // [NOTE] // NOCACHE_PATH_PREFIX symbol needs for not using cache mode. // Now s3fs I/F functions in s3fs.cpp has left the processing // to FdManager and FdEntity class. FdManager class manages // the list of local file stat and file descriptor in conjunction // with the FdEntity class. // When s3fs is not using local cache, it means FdManager must // return new temporary file descriptor at each opening it. // Then FdManager caches fd by key which is dummy file path // instead of real file path. // This process may not be complete, but it is easy way can // be realized. // static constexpr char NOCACHE_PATH_PREFIX_FORM[] = " __S3FS_UNEXISTED_PATH_%lx__ / "; // important space words for simply //------------------------------------------------ // FdManager class variable //------------------------------------------------ FdManager FdManager::singleton; pthread_mutex_t FdManager::fd_manager_lock; pthread_mutex_t FdManager::cache_cleanup_lock; pthread_mutex_t FdManager::reserved_diskspace_lock; bool FdManager::is_lock_init(false); std::string FdManager::cache_dir; bool FdManager::check_cache_dir_exist(false); off_t FdManager::free_disk_space = 0; off_t FdManager::fake_used_disk_space = 0; std::string FdManager::check_cache_output; bool FdManager::checked_lseek(false); bool FdManager::have_lseek_hole(false); std::string FdManager::tmp_dir = "/tmp"; //------------------------------------------------ // FdManager class methods //------------------------------------------------ bool FdManager::SetCacheDir(const char* dir) { if(!dir || '\0' == dir[0]){ cache_dir = ""; }else{ cache_dir = dir; } return true; } bool FdManager::SetCacheCheckOutput(const char* path) { if(!path || '\0' == path[0]){ check_cache_output.erase(); }else{ check_cache_output = path; } return true; } bool FdManager::DeleteCacheDirectory() { if(FdManager::cache_dir.empty()){ return true; } std::string cache_path; if(!FdManager::MakeCachePath(nullptr, cache_path, false)){ return false; } if(!delete_files_in_dir(cache_path.c_str(), true)){ return false; } std::string mirror_path = FdManager::cache_dir + "/." + S3fsCred::GetBucket() + ".mirror"; if(!delete_files_in_dir(mirror_path.c_str(), true)){ return false; } return true; } int FdManager::DeleteCacheFile(const char* path) { S3FS_PRN_INFO3("[path=%s]", SAFESTRPTR(path)); if(!path){ return -EIO; } if(FdManager::cache_dir.empty()){ return 0; } std::string cache_path; if(!FdManager::MakeCachePath(path, cache_path, false)){ return 0; } int result = 0; if(0 != unlink(cache_path.c_str())){ if(ENOENT == errno){ S3FS_PRN_DBG("failed to delete file(%s): errno=%d", path, errno); }else{ S3FS_PRN_ERR("failed to delete file(%s): errno=%d", path, errno); } return -errno; } if(0 != (result = CacheFileStat::DeleteCacheFileStat(path))){ if(-ENOENT == result){ S3FS_PRN_DBG("failed to delete stat file(%s): errno=%d", path, result); }else{ S3FS_PRN_ERR("failed to delete stat file(%s): errno=%d", path, result); } } return result; } bool FdManager::MakeCachePath(const char* path, std::string& cache_path, bool is_create_dir, bool is_mirror_path) { if(FdManager::cache_dir.empty()){ cache_path = ""; return true; } std::string resolved_path(FdManager::cache_dir); if(!is_mirror_path){ resolved_path += "/"; resolved_path += S3fsCred::GetBucket(); }else{ resolved_path += "/."; resolved_path += S3fsCred::GetBucket(); resolved_path += ".mirror"; } if(is_create_dir){ int result; if(0 != (result = mkdirp(resolved_path + mydirname(path), 0777))){ S3FS_PRN_ERR("failed to create dir(%s) by errno(%d).", path, result); return false; } } if(!path || '\0' == path[0]){ cache_path = resolved_path; }else{ cache_path = resolved_path + SAFESTRPTR(path); } return true; } bool FdManager::CheckCacheTopDir() { if(FdManager::cache_dir.empty()){ return true; } std::string toppath(FdManager::cache_dir + "/" + S3fsCred::GetBucket()); return check_exist_dir_permission(toppath.c_str()); } bool FdManager::MakeRandomTempPath(const char* path, std::string& tmppath) { char szBuff[64]; snprintf(szBuff, sizeof(szBuff), NOCACHE_PATH_PREFIX_FORM, random()); // worry for performance, but maybe don't worry. szBuff[sizeof(szBuff) - 1] = '\0'; // for safety tmppath = szBuff; tmppath += path ? path : ""; return true; } bool FdManager::SetCheckCacheDirExist(bool is_check) { bool old = FdManager::check_cache_dir_exist; FdManager::check_cache_dir_exist = is_check; return old; } bool FdManager::CheckCacheDirExist() { if(!FdManager::check_cache_dir_exist){ return true; } if(FdManager::cache_dir.empty()){ return true; } return IsDir(&cache_dir); } off_t FdManager::GetEnsureFreeDiskSpace() { AutoLock auto_lock(&FdManager::reserved_diskspace_lock); return FdManager::free_disk_space; } off_t FdManager::SetEnsureFreeDiskSpace(off_t size) { AutoLock auto_lock(&FdManager::reserved_diskspace_lock); off_t old = FdManager::free_disk_space; FdManager::free_disk_space = size; return old; } bool FdManager::InitFakeUsedDiskSize(off_t fake_freesize) { FdManager::fake_used_disk_space = 0; // At first, clear this value because this value is used in GetFreeDiskSpace. off_t actual_freesize = FdManager::GetFreeDiskSpace(nullptr); if(fake_freesize < actual_freesize){ FdManager::fake_used_disk_space = actual_freesize - fake_freesize; }else{ FdManager::fake_used_disk_space = 0; } return true; } off_t FdManager::GetTotalDiskSpaceByRatio(int ratio) { return FdManager::GetTotalDiskSpace(nullptr) * ratio / 100; } off_t FdManager::GetTotalDiskSpace(const char* path) { struct statvfs vfsbuf; int result = FdManager::GetVfsStat(path, &vfsbuf); if(result == -1){ return 0; } off_t actual_totalsize = vfsbuf.f_blocks * vfsbuf.f_frsize; return actual_totalsize; } off_t FdManager::GetFreeDiskSpace(const char* path) { struct statvfs vfsbuf; int result = FdManager::GetVfsStat(path, &vfsbuf); if(result == -1){ return 0; } off_t actual_freesize = vfsbuf.f_bavail * vfsbuf.f_frsize; return (FdManager::fake_used_disk_space < actual_freesize ? (actual_freesize - FdManager::fake_used_disk_space) : 0); } int FdManager::GetVfsStat(const char* path, struct statvfs* vfsbuf){ std::string ctoppath; if(!FdManager::cache_dir.empty()){ ctoppath = FdManager::cache_dir + "/"; ctoppath = get_exist_directory_path(ctoppath); // existed directory if(ctoppath != "/"){ ctoppath += "/"; } }else{ ctoppath = tmp_dir + "/"; } if(path && '\0' != *path){ ctoppath += path; }else{ ctoppath += "."; } if(-1 == statvfs(ctoppath.c_str(), vfsbuf)){ S3FS_PRN_ERR("could not get vfs stat by errno(%d)", errno); return -1; } return 0; } bool FdManager::IsSafeDiskSpace(const char* path, off_t size) { off_t fsize = FdManager::GetFreeDiskSpace(path); return size + FdManager::GetEnsureFreeDiskSpace() <= fsize; } bool FdManager::IsSafeDiskSpaceWithLog(const char* path, off_t size) { off_t fsize = FdManager::GetFreeDiskSpace(path); off_t needsize = size + FdManager::GetEnsureFreeDiskSpace(); if(needsize <= fsize){ return true; } else { S3FS_PRN_EXIT("There is no enough disk space for used as cache(or temporary) directory by s3fs. Requires %.3f MB, already has %.3f MB.", static_cast(needsize) / 1024 / 1024, static_cast(fsize) / 1024 / 1024); return false; } } bool FdManager::HaveLseekHole() { if(FdManager::checked_lseek){ return FdManager::have_lseek_hole; } // create temporary file int fd; std::unique_ptr ptmpfp(MakeTempFile(), &s3fs_fclose); if(nullptr == ptmpfp || -1 == (fd = fileno(ptmpfp.get()))){ S3FS_PRN_ERR("failed to open temporary file by errno(%d)", errno); FdManager::checked_lseek = true; FdManager::have_lseek_hole = false; return false; } // check SEEK_DATA/SEEK_HOLE options bool result = true; if(-1 == lseek(fd, 0, SEEK_DATA)){ if(EINVAL == errno){ S3FS_PRN_ERR("lseek does not support SEEK_DATA"); result = false; } } if(result && -1 == lseek(fd, 0, SEEK_HOLE)){ if(EINVAL == errno){ S3FS_PRN_ERR("lseek does not support SEEK_HOLE"); result = false; } } FdManager::checked_lseek = true; FdManager::have_lseek_hole = result; return FdManager::have_lseek_hole; } bool FdManager::SetTmpDir(const char *dir) { if(!dir || '\0' == dir[0]){ tmp_dir = "/tmp"; }else{ tmp_dir = dir; } return true; } bool FdManager::IsDir(const std::string* dir) { // check the directory struct stat st; if(0 != stat(dir->c_str(), &st)){ S3FS_PRN_ERR("could not stat() directory %s by errno(%d).", dir->c_str(), errno); return false; } if(!S_ISDIR(st.st_mode)){ S3FS_PRN_ERR("the directory %s is not a directory.", dir->c_str()); return false; } return true; } bool FdManager::CheckTmpDirExist() { if(FdManager::tmp_dir.empty()){ return true; } return IsDir(&tmp_dir); } FILE* FdManager::MakeTempFile() { int fd; char cfn[PATH_MAX]; std::string fn = tmp_dir + "/s3fstmp.XXXXXX"; strncpy(cfn, fn.c_str(), sizeof(cfn) - 1); cfn[sizeof(cfn) - 1] = '\0'; fd = mkstemp(cfn); if (-1 == fd) { S3FS_PRN_ERR("failed to create tmp file. errno(%d)", errno); return nullptr; } if (-1 == unlink(cfn)) { S3FS_PRN_ERR("failed to delete tmp file. errno(%d)", errno); return nullptr; } return fdopen(fd, "rb+"); } bool FdManager::HasOpenEntityFd(const char* path) { AutoLock auto_lock(&FdManager::fd_manager_lock); const FdEntity* ent; int fd = -1; if(nullptr == (ent = FdManager::singleton.GetFdEntity(path, fd, false, AutoLock::ALREADY_LOCKED))){ return false; } return (0 < ent->GetOpenCount()); } // [NOTE] // Returns the number of open pseudo fd. // int FdManager::GetOpenFdCount(const char* path) { AutoLock auto_lock(&FdManager::fd_manager_lock); return FdManager::singleton.GetPseudoFdCount(path); } //------------------------------------------------ // FdManager methods //------------------------------------------------ FdManager::FdManager() { if(this == FdManager::get()){ pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); #if S3FS_PTHREAD_ERRORCHECK pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); #endif int result; if(0 != (result = pthread_mutex_init(&FdManager::fd_manager_lock, &attr))){ S3FS_PRN_CRIT("failed to init fd_manager_lock: %d", result); abort(); } if(0 != (result = pthread_mutex_init(&FdManager::cache_cleanup_lock, &attr))){ S3FS_PRN_CRIT("failed to init cache_cleanup_lock: %d", result); abort(); } if(0 != (result = pthread_mutex_init(&FdManager::reserved_diskspace_lock, &attr))){ S3FS_PRN_CRIT("failed to init reserved_diskspace_lock: %d", result); abort(); } FdManager::is_lock_init = true; }else{ abort(); } } FdManager::~FdManager() { if(this == FdManager::get()){ for(fdent_map_t::iterator iter = fent.begin(); fent.end() != iter; ++iter){ FdEntity* ent = (*iter).second.get(); S3FS_PRN_WARN("To exit with the cache file opened: path=%s, refcnt=%d", ent->GetPath().c_str(), ent->GetOpenCount()); } fent.clear(); if(FdManager::is_lock_init){ int result; if(0 != (result = pthread_mutex_destroy(&FdManager::fd_manager_lock))){ S3FS_PRN_CRIT("failed to destroy fd_manager_lock: %d", result); abort(); } if(0 != (result = pthread_mutex_destroy(&FdManager::cache_cleanup_lock))){ S3FS_PRN_CRIT("failed to destroy cache_cleanup_lock: %d", result); abort(); } if(0 != (result = pthread_mutex_destroy(&FdManager::reserved_diskspace_lock))){ S3FS_PRN_CRIT("failed to destroy reserved_diskspace_lock: %d", result); abort(); } FdManager::is_lock_init = false; } }else{ abort(); } } FdEntity* FdManager::GetFdEntity(const char* path, int& existfd, bool newfd, AutoLock::Type locktype) { S3FS_PRN_INFO3("[path=%s][pseudo_fd=%d]", SAFESTRPTR(path), existfd); if(!path || '\0' == path[0]){ return nullptr; } AutoLock auto_lock(&FdManager::fd_manager_lock, locktype); fdent_map_t::iterator iter = fent.find(path); if(fent.end() != iter && iter->second){ if(-1 == existfd){ if(newfd){ existfd = iter->second->OpenPseudoFd(O_RDWR); // [NOTE] O_RDWR flags } return iter->second.get(); }else if(iter->second->FindPseudoFd(existfd)){ if(newfd){ existfd = iter->second->Dup(existfd); } return iter->second.get(); } } if(-1 != existfd){ for(iter = fent.begin(); iter != fent.end(); ++iter){ if(iter->second && iter->second->FindPseudoFd(existfd)){ // found opened fd in map if(iter->second->GetPath() == path){ if(newfd){ existfd = iter->second->Dup(existfd); } return iter->second.get(); } // found fd, but it is used another file(file descriptor is recycled) // so returns nullptr. break; } } } // If the cache directory is not specified, s3fs opens a temporary file // when the file is opened. if(!FdManager::IsCacheDir()){ for(iter = fent.begin(); iter != fent.end(); ++iter){ if(iter->second && iter->second->IsOpen() && iter->second->GetPath() == path){ return iter->second.get(); } } } return nullptr; } FdEntity* FdManager::Open(int& fd, const char* path, const headers_t* pmeta, off_t size, const struct timespec& ts_mctime, int flags, bool force_tmpfile, bool is_create, bool ignore_modify, AutoLock::Type type) { S3FS_PRN_DBG("[path=%s][size=%lld][ts_mctime=%s][flags=0x%x][force_tmpfile=%s][create=%s][ignore_modify=%s]", SAFESTRPTR(path), static_cast(size), str(ts_mctime).c_str(), flags, (force_tmpfile ? "yes" : "no"), (is_create ? "yes" : "no"), (ignore_modify ? "yes" : "no")); if(!path || '\0' == path[0]){ return nullptr; } AutoLock auto_lock(&FdManager::fd_manager_lock); // search in mapping by key(path) fdent_map_t::iterator iter = fent.find(path); if(fent.end() == iter && !force_tmpfile && !FdManager::IsCacheDir()){ // If the cache directory is not specified, s3fs opens a temporary file // when the file is opened. // Then if it could not find a entity in map for the file, s3fs should // search a entity in all which opened the temporary file. // for(iter = fent.begin(); iter != fent.end(); ++iter){ if(iter->second && iter->second->IsOpen() && iter->second->GetPath() == path){ break; // found opened fd in mapping } } } if(fent.end() != iter){ // found FdEntity* ent = iter->second.get(); // [NOTE] // If the file is being modified and ignore_modify flag is false, // the file size will not be changed even if there is a request // to reduce the size of the modified file. // If you do, the "test_open_second_fd" test will fail. // if(!ignore_modify && ent->IsModified()){ // If the file is being modified and it's size is larger than size parameter, it will not be resized. off_t cur_size = 0; if(ent->GetSize(cur_size) && size <= cur_size){ size = -1; } } // (re)open if(0 > (fd = ent->Open(pmeta, size, ts_mctime, flags, type))){ S3FS_PRN_ERR("failed to (re)open and create new pseudo fd for path(%s).", path); return nullptr; } if(use_newcache){ accessor->Invalidate(path); } return ent; }else if(is_create){ // not found std::string cache_path; if(!force_tmpfile && !FdManager::MakeCachePath(path, cache_path, true)){ S3FS_PRN_ERR("failed to make cache path for object(%s).", path); return nullptr; } // make new obj std::unique_ptr ent(new FdEntity(path, cache_path.c_str())); // open if(0 > (fd = ent->Open(pmeta, size, ts_mctime, flags, type))){ S3FS_PRN_ERR("failed to open and create new pseudo fd for path(%s) errno:%d.", path, fd); return nullptr; } if(use_newcache){ ent->UpdateRealsize(size); } if(!cache_path.empty()){ // using cache return (fent[path] = std::move(ent)).get(); }else{ // not using cache, so the key of fdentity is set not really existing path. // (but not strictly unexisting path.) // // [NOTE] // The reason why this process here, please look at the definition of the // comments of NOCACHE_PATH_PREFIX_FORM symbol. // std::string tmppath; FdManager::MakeRandomTempPath(path, tmppath); return (fent[tmppath] = std::move(ent)).get(); } }else{ return nullptr; } } // [NOTE] // This method does not create a new pseudo fd. // It just finds existfd and returns the corresponding entity. // FdEntity* FdManager::GetExistFdEntity(const char* path, int existfd) { S3FS_PRN_DBG("[path=%s][pseudo_fd=%d]", SAFESTRPTR(path), existfd); AutoLock auto_lock(&FdManager::fd_manager_lock); // search from all entity. for(fdent_map_t::iterator iter = fent.begin(); iter != fent.end(); ++iter){ if(iter->second && iter->second->FindPseudoFd(existfd)){ // found existfd in entity return iter->second.get(); } } // not found entity return nullptr; } FdEntity* FdManager::OpenExistFdEntity(const char* path, int& fd, int flags) { S3FS_PRN_DBG("[path=%s][flags=0x%x]", SAFESTRPTR(path), flags); // search entity by path, and create pseudo fd FdEntity* ent = Open(fd, path, nullptr, -1, S3FS_OMIT_TS, flags, false, false, false, AutoLock::NONE); if(!ent){ // Not found entity return nullptr; } return ent; } // [NOTE] // Returns the number of open pseudo fd. // This method is called from GetOpenFdCount method which is already locked. // int FdManager::GetPseudoFdCount(const char* path) { S3FS_PRN_DBG("[path=%s]", SAFESTRPTR(path)); if(!path || '\0' == path[0]){ return 0; } // search from all entity. for(fdent_map_t::iterator iter = fent.begin(); iter != fent.end(); ++iter){ if(iter->second && iter->second->GetPath() == path){ // found the entity for the path return iter->second->GetOpenCount(); } } // not found entity return 0; } void FdManager::Rename(const std::string &from, const std::string &to) { AutoLock auto_lock(&FdManager::fd_manager_lock); fdent_map_t::iterator iter = fent.find(from); if(fent.end() == iter && !FdManager::IsCacheDir()){ // If the cache directory is not specified, s3fs opens a temporary file // when the file is opened. // Then if it could not find a entity in map for the file, s3fs should // search a entity in all which opened the temporary file. // for(iter = fent.begin(); iter != fent.end(); ++iter){ if(iter->second && iter->second->IsOpen() && iter->second->GetPath() == from){ break; // found opened fd in mapping } } } if(fent.end() != iter){ // found S3FS_PRN_DBG("[from=%s][to=%s]", from.c_str(), to.c_str()); std::unique_ptr ent(std::move(iter->second)); // retrieve old fd entity from map fent.erase(iter); // rename path and caches in fd entity std::string fentmapkey; if(!ent->RenamePath(to, fentmapkey)){ S3FS_PRN_ERR("Failed to rename FdEntity object for %s to %s", from.c_str(), to.c_str()); return; } // set new fd entity to map fent[fentmapkey] = std::move(ent); } } bool FdManager::Close(FdEntity* ent, int fd) { S3FS_PRN_DBG("[ent->file=%s][pseudo_fd=%d]", ent ? ent->GetPath().c_str() : "", fd); if(!ent || -1 == fd){ return true; // returns success } AutoLock auto_lock(&FdManager::fd_manager_lock); for(fdent_map_t::iterator iter = fent.begin(); iter != fent.end(); ++iter){ if(iter->second.get() == ent){ ent->Close(fd); if(!ent->IsOpen()){ // remove found entity from map. iter = fent.erase(iter); // check another key name for entity value to be on the safe side for(; iter != fent.end(); ){ if(iter->second.get() == ent){ iter = fent.erase(iter); }else{ ++iter; } } } return true; } } return false; } bool FdManager::ChangeEntityToTempPath(FdEntity* ent, const char* path) { AutoLock auto_lock(&FdManager::fd_manager_lock); for(fdent_map_t::iterator iter = fent.begin(); iter != fent.end(); ){ if(iter->second.get() == ent){ std::string tmppath; FdManager::MakeRandomTempPath(path, tmppath); iter->second.reset(ent); break; }else{ ++iter; } } return false; } void FdManager::CleanupCacheDir() { //S3FS_PRN_DBG("cache cleanup requested"); if(!FdManager::IsCacheDir()){ return; } AutoLock auto_lock_no_wait(&FdManager::cache_cleanup_lock, AutoLock::NO_WAIT); if(auto_lock_no_wait.isLockAcquired()){ //S3FS_PRN_DBG("cache cleanup started"); CleanupCacheDirInternal(""); //S3FS_PRN_DBG("cache cleanup ended"); }else{ // wait for other thread to finish cache cleanup AutoLock auto_lock(&FdManager::cache_cleanup_lock); } } void FdManager::CleanupCacheDirInternal(const std::string &path) { DIR* dp; struct dirent* dent; std::string abs_path = cache_dir + "/" + S3fsCred::GetBucket() + path; if(nullptr == (dp = opendir(abs_path.c_str()))){ S3FS_PRN_ERR("could not open cache dir(%s) - errno(%d)", abs_path.c_str(), errno); return; } for(dent = readdir(dp); dent; dent = readdir(dp)){ if(0 == strcmp(dent->d_name, "..") || 0 == strcmp(dent->d_name, ".")){ continue; } std::string fullpath = abs_path; fullpath += "/"; fullpath += dent->d_name; struct stat st; if(0 != lstat(fullpath.c_str(), &st)){ S3FS_PRN_ERR("could not get stats of file(%s) - errno(%d)", fullpath.c_str(), errno); closedir(dp); return; } std::string next_path = path + "/" + dent->d_name; if(S_ISDIR(st.st_mode)){ CleanupCacheDirInternal(next_path); }else{ AutoLock auto_lock(&FdManager::fd_manager_lock, AutoLock::NO_WAIT); if (!auto_lock.isLockAcquired()) { S3FS_PRN_INFO("could not get fd_manager_lock when clean up file(%s), then skip it.", next_path.c_str()); continue; } fdent_map_t::iterator iter = fent.find(next_path); if(fent.end() == iter) { S3FS_PRN_DBG("cleaned up: %s", next_path.c_str()); FdManager::DeleteCacheFile(next_path.c_str()); } } } closedir(dp); } bool FdManager::ReserveDiskSpace(off_t size) { if(IsSafeDiskSpace(nullptr, size)){ AutoLock auto_lock(&FdManager::reserved_diskspace_lock); free_disk_space += size; return true; } return false; } void FdManager::FreeReservedDiskSpace(off_t size) { AutoLock auto_lock(&FdManager::reserved_diskspace_lock); free_disk_space -= size; } // // Inspect all files for stats file for cache file // // [NOTE] // The minimum sub_path parameter is "/". // The sub_path is a directory path starting from "/" and ending with "/". // // This method produces the following output. // // * Header // ------------------------------------------------------------ // Check cache file and its stats file consistency // ------------------------------------------------------------ // * When the cache file and its stats information match // File path: -> [OK] no problem // // * If there is a problem with the cache file and its stats information // File path: // -> [P] // -> [E] there is a mark that data exists in stats, but there is no data in the cache file. // (bytes) // ... // ... // -> [W] These show no data in stats, but there is evidence of data in the cache file.(no problem.) // (bytes) // ... // ... // bool FdManager::RawCheckAllCache(FILE* fp, const char* cache_stat_top_dir, const char* sub_path, int& total_file_cnt, int& err_file_cnt, int& err_dir_cnt) { if(!cache_stat_top_dir || '\0' == cache_stat_top_dir[0] || !sub_path || '\0' == sub_path[0]){ S3FS_PRN_ERR("Parameter cache_stat_top_dir is empty."); return false; } // open directory of cache file's stats DIR* statsdir; std::string target_dir = cache_stat_top_dir; target_dir += sub_path; if(nullptr == (statsdir = opendir(target_dir.c_str()))){ S3FS_PRN_ERR("Could not open directory(%s) by errno(%d)", target_dir.c_str(), errno); return false; } // loop in directory of cache file's stats const struct dirent* pdirent = nullptr; while(nullptr != (pdirent = readdir(statsdir))){ if(DT_DIR == pdirent->d_type){ // found directory if(0 == strcmp(pdirent->d_name, ".") || 0 == strcmp(pdirent->d_name, "..")){ continue; } // reentrant for sub directory std::string subdir_path = sub_path; subdir_path += pdirent->d_name; subdir_path += '/'; if(!RawCheckAllCache(fp, cache_stat_top_dir, subdir_path.c_str(), total_file_cnt, err_file_cnt, err_dir_cnt)){ // put error message for this dir. ++err_dir_cnt; S3FS_PRN_CACHE(fp, CACHEDBG_FMT_DIR_PROB, subdir_path.c_str()); S3FS_PRN_CACHE(fp, CACHEDBG_FMT_CRIT_HEAD, "Something error is occurred in checking this directory"); } }else{ ++total_file_cnt; // make cache file path std::string strOpenedWarn; std::string cache_path; std::string object_file_path = sub_path; object_file_path += pdirent->d_name; if(!FdManager::MakeCachePath(object_file_path.c_str(), cache_path, false, false) || cache_path.empty()){ ++err_file_cnt; S3FS_PRN_CACHE(fp, CACHEDBG_FMT_FILE_PROB, object_file_path.c_str(), strOpenedWarn.c_str()); S3FS_PRN_CACHE(fp, CACHEDBG_FMT_CRIT_HEAD, "Could not make cache file path"); continue; } // check if the target file is currently in operation. { AutoLock auto_lock(&FdManager::fd_manager_lock); fdent_map_t::iterator iter = fent.find(object_file_path); if(fent.end() != iter){ // This file is opened now, then we need to put warning message. strOpenedWarn = CACHEDBG_FMT_WARN_OPEN; } } // open cache file int cache_file_fd; if(-1 == (cache_file_fd = open(cache_path.c_str(), O_RDONLY))){ ++err_file_cnt; S3FS_PRN_CACHE(fp, CACHEDBG_FMT_FILE_PROB, object_file_path.c_str(), strOpenedWarn.c_str()); S3FS_PRN_CACHE(fp, CACHEDBG_FMT_CRIT_HEAD, "Could not open cache file"); continue; } scope_guard guard([&]() { close(cache_file_fd); }); // get inode number for cache file struct stat st; if(0 != fstat(cache_file_fd, &st)){ ++err_file_cnt; S3FS_PRN_CACHE(fp, CACHEDBG_FMT_FILE_PROB, object_file_path.c_str(), strOpenedWarn.c_str()); S3FS_PRN_CACHE(fp, CACHEDBG_FMT_CRIT_HEAD, "Could not get file inode number for cache file"); continue; } ino_t cache_file_inode = st.st_ino; // open cache stat file and load page info. PageList pagelist; CacheFileStat cfstat(object_file_path.c_str()); if(!cfstat.ReadOnlyOpen() || !pagelist.Serialize(cfstat, false, cache_file_inode)){ ++err_file_cnt; S3FS_PRN_CACHE(fp, CACHEDBG_FMT_FILE_PROB, object_file_path.c_str(), strOpenedWarn.c_str()); S3FS_PRN_CACHE(fp, CACHEDBG_FMT_CRIT_HEAD, "Could not load cache file stats information"); continue; } cfstat.Release(); // compare cache file size and stats information if(st.st_size != pagelist.Size()){ ++err_file_cnt; S3FS_PRN_CACHE(fp, CACHEDBG_FMT_FILE_PROB, object_file_path.c_str(), strOpenedWarn.c_str()); S3FS_PRN_CACHE(fp, CACHEDBG_FMT_CRIT_HEAD2 "The cache file size(%lld) and the value(%lld) from cache file stats are different", static_cast(st.st_size), static_cast(pagelist.Size())); continue; } // compare cache file stats and cache file blocks fdpage_list_t err_area_list; fdpage_list_t warn_area_list; if(!pagelist.CompareSparseFile(cache_file_fd, st.st_size, err_area_list, warn_area_list)){ // Found some error or warning S3FS_PRN_CACHE(fp, CACHEDBG_FMT_FILE_PROB, object_file_path.c_str(), strOpenedWarn.c_str()); if(!warn_area_list.empty()){ S3FS_PRN_CACHE(fp, CACHEDBG_FMT_WARN_HEAD); for(fdpage_list_t::const_iterator witer = warn_area_list.begin(); witer != warn_area_list.end(); ++witer){ S3FS_PRN_CACHE(fp, CACHEDBG_FMT_PROB_BLOCK, static_cast(witer->offset), static_cast(witer->bytes)); } } if(!err_area_list.empty()){ ++err_file_cnt; S3FS_PRN_CACHE(fp, CACHEDBG_FMT_ERR_HEAD); for(fdpage_list_t::const_iterator eiter = err_area_list.begin(); eiter != err_area_list.end(); ++eiter){ S3FS_PRN_CACHE(fp, CACHEDBG_FMT_PROB_BLOCK, static_cast(eiter->offset), static_cast(eiter->bytes)); } } }else{ // There is no problem! if(!strOpenedWarn.empty()){ strOpenedWarn += "\n "; } S3FS_PRN_CACHE(fp, CACHEDBG_FMT_FILE_OK, object_file_path.c_str(), strOpenedWarn.c_str()); } err_area_list.clear(); warn_area_list.clear(); } } closedir(statsdir); return true; } bool FdManager::CheckAllCache() { if(!FdManager::HaveLseekHole()){ S3FS_PRN_ERR("lseek does not support SEEK_DATA/SEEK_HOLE, then could not check cache."); return false; } FILE* fp; if(FdManager::check_cache_output.empty()){ fp = stdout; }else{ if(nullptr == (fp = fopen(FdManager::check_cache_output.c_str(), "a+"))){ S3FS_PRN_ERR("Could not open(create) output file(%s) for checking all cache by errno(%d)", FdManager::check_cache_output.c_str(), errno); return false; } } // print head message S3FS_PRN_CACHE(fp, CACHEDBG_FMT_HEAD, S3fsLog::GetCurrentTime().c_str()); // Loop in directory of cache file's stats std::string top_path = CacheFileStat::GetCacheFileStatTopDir(); int total_file_cnt = 0; int err_file_cnt = 0; int err_dir_cnt = 0; bool result = RawCheckAllCache(fp, top_path.c_str(), "/", total_file_cnt, err_file_cnt, err_dir_cnt); if(!result){ S3FS_PRN_ERR("Processing failed due to some problem."); } // print foot message S3FS_PRN_CACHE(fp, CACHEDBG_FMT_FOOT, total_file_cnt, err_file_cnt, err_dir_cnt); if(stdout != fp){ fclose(fp); } return result; } void FdManager::ReleaseCache(const std::string &avoid_path, off_t size, const std::string &dir) { DIR* dp; struct dirent* dent; std::string abs_path = cache_dir + "/" + S3fsCred::GetBucket() + dir; if(nullptr == (dp = opendir(abs_path.c_str()))){ S3FS_PRN_ERR("could not open cache dir(%s) - errno(%d)", abs_path.c_str(), errno); return; } for(dent = readdir(dp); dent; dent = readdir(dp)){ if(GetFreeDiskSpace(nullptr) >= size) return; if(0 == strcmp(dent->d_name, "..") || 0 == strcmp(dent->d_name, ".")){ continue; } std::string fullpath = abs_path; fullpath += "/"; fullpath += dent->d_name; struct stat st; if(0 != lstat(fullpath.c_str(), &st)){ S3FS_PRN_ERR("could not get stats of file(%s) - errno(%d)", fullpath.c_str(), errno); continue; } std::string next_path = dir + "/" + dent->d_name; if(next_path == avoid_path) continue; if(S_ISDIR(st.st_mode)){ ReleaseCache(avoid_path, size, next_path); }else{ AutoLock auto_lock(&FdManager::fd_manager_lock, AutoLock::NO_WAIT); if (!auto_lock.isLockAcquired()) { S3FS_PRN_INFO("could not get fd_manager_lock when clean up file(%s), then skip it.", next_path.c_str()); continue; } fdent_map_t::iterator iter = fent.find(next_path); if(fent.end() == iter) { S3FS_PRN_DBG("cleaned up: %s", next_path.c_str()); FdManager::DeleteCacheFile(next_path.c_str()); }else{ FdEntity* ent = (*iter).second.get(); ent->ReleaseCache(); } } } closedir(dp); } bool FdManager::EnsureDiskSpaceUsable(const std::string &avoid_path, off_t size) { if(GetFreeDiskSpace(nullptr) >= size) return true; ReleaseCache(avoid_path, size, ""); return GetFreeDiskSpace(nullptr) >= size; } /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: expandtab sw=4 ts=4 fdm=marker * vim<600: expandtab sw=4 ts=4 */