//------------------------------------------------------------------------------ //! @file data.cc //! @author Andreas-Joachim Peters CERN //! @brief class keeping dir contents at a given level //------------------------------------------------------------------------------ /************************************************************************ * EOS - the CERN Disk Storage System * * Copyright (C) 2016 CERN/Switzerland * * * * 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 3 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, see .* ************************************************************************/ #include "data/dircleaner.hh" #undef __USE_FILE_OFFSET64 #include #define __USE_FILE_OFFSET64 #include /* -------------------------------------------------------------------------- */ dircleaner::dircleaner(const std::string _path, const std::string _name, int64_t _maxsize, int64_t _maxfiles, float _clean_threshold) : path(_path), max_files(_maxfiles), max_size(_maxsize), clean_threshold(_clean_threshold), name(_name) { if (max_files | max_size) { tLeveler.reset(&dircleaner::leveler, this); } } /* -------------------------------------------------------------------------- */ dircleaner::~dircleaner() /* -------------------------------------------------------------------------- */ { } /* -------------------------------------------------------------------------- */ bool /* -------------------------------------------------------------------------- */ dircleaner::has_suffix(const std::string& str, const std::string& suffix) /* -------------------------------------------------------------------------- */ { return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; } /* -------------------------------------------------------------------------- */ int /* -------------------------------------------------------------------------- */ dircleaner::cleanall(std::string matchsuffix) /* -------------------------------------------------------------------------- */ { std::lock_guard mLock(cleaningMutex); if (!scanall(matchsuffix)) { std::string tout; treeinfo.Print(tout); eos_static_info("[ %s ] purging %s", name.c_str(), tout.c_str()); for (auto it = treeinfo.treemap.begin(); it != treeinfo.treemap.end(); ++it) { if (matchsuffix.length() && (!has_suffix(it->second.path, matchsuffix))) { continue; } if (unlink(it->second.path.c_str()) && errno != ENOENT) { eos_static_err("[ %s ] unlink: path=%s errno=%d", name.c_str(), it->second.path.c_str(), errno); } } } return 0; } /* -------------------------------------------------------------------------- */ void /* -------------------------------------------------------------------------- */ dircleaner::tree_info::Print(std::string& out) /* -------------------------------------------------------------------------- */ { char line[1024]; snprintf(line, sizeof(line), "path=%s n-files=%lu tree-size=%lu", path.c_str(), totalfiles, totalsize); out += line; } /* -------------------------------------------------------------------------- */ int /* -------------------------------------------------------------------------- */ dircleaner::scanall(std::string matchsuffix) /* -------------------------------------------------------------------------- */ { int retc = 0; char* paths[2]; paths[0] = (char*) path.c_str(); paths[1] = 0; FTS* tree = fts_open(paths, FTS_NOCHDIR, 0); if (!tree) { return -1; // see errno } FTSENT* node; treeinfo.path = path; treeinfo.totalfiles = 0; treeinfo.totalsize = 0; treeinfo.treemap.clear(); externaltreeinfo.reset(); while ((node = fts_read(tree))) { // skip any hidden file, we might want that if (node->fts_level > 0 && node->fts_name[0] == '.') { fts_set(tree, node, FTS_SKIP); } else { if (node->fts_info == FTS_F) { std::string filepath = node->fts_accpath; if (matchsuffix.length() && !has_suffix(filepath, matchsuffix)) { continue; } struct stat buf; if (::stat(filepath.c_str(), &buf)) { if (errno == ENOENT) { // this can happen when something get's cleaned during scanning eos_static_info("[ %s ] stat: path=%s errno=%d", name.c_str(), filepath.c_str(), errno); } else { eos_static_err("[ %s ] stat: path=%s errno=%d", name.c_str(), filepath.c_str(), errno); retc = -1; } } else { treeinfo.totalfiles++; treeinfo.totalsize += buf.st_size; file_info_t finfo; finfo.path = filepath; finfo.mtime = buf.st_mtime; finfo.size = buf.st_size; treeinfo.treemap.insert(std::pair(finfo.mtime, finfo)); eos_static_debug("[ %s ] adding path=%s mtime=%lu size=%lu", name.c_str(), filepath.c_str(), buf.st_mtime, buf.st_size); } } else if (node->fts_info == FTS_D) { std::string dirpath = node->fts_accpath; struct stat buf; if (node->fts_level > 0) { if (EOS_LOGS_DEBUG) { eos_static_debug("checking directory %s", dirpath.c_str()); } if (!::stat(dirpath.c_str(), &buf)) { time_t now = time(NULL); if ( now > (buf.st_ctime - 60) ) { // avoid the unforunate race when the diskcache/or journal needs to create a parent directory for a file // try to delete - will work if it is empty if (!::rmdir(dirpath.c_str())) { eos_static_notice("[ %s ] rmdir: empty inode directory", dirpath.c_str()); } } } } } } } if (fts_close(tree)) { eos_static_err("[ %s ] fts_close: errno=%d", name.c_str(), errno); return -1; } return retc; } /* -------------------------------------------------------------------------- */ int /* -------------------------------------------------------------------------- */ dircleaner::trim(bool force) { if (!force) { // avoid full scans tree_info_t& externaltree = get_external_tree(); bool size_ok = true; bool files_ok = true; // an external cache can give change hints via the externaltree int64_t tree_size = treeinfo.get_size() + externaltree.get_size(); int64_t tree_files = treeinfo.get_files() + externaltree.get_files(); eos_static_info("[ %s ] max-size=%ld is-size=%ld max-files=%lld is-files=%ld force=%d", name.c_str(), max_size, tree_size, max_files, tree_files, force); if (max_size && (tree_size > max_size)) { size_ok = false; } if (max_files && (tree_files > max_files)) { files_ok = false; } // nothing to do if (size_ok && files_ok) { return 0; } } scanall(trim_suffix); for (auto it = treeinfo.treemap.begin(); it != treeinfo.treemap.end(); ++it) { eos_static_debug("[ %s ] is-size %lld max-size %lld", name.c_str(), treeinfo.get_size(), max_size); bool size_ok = true; bool files_ok = true; if (max_size && (treeinfo.get_size() > max_size)) { size_ok = false; } if (max_files && (treeinfo.get_files() > max_files)) { files_ok = false; } // nothing to do if (size_ok && files_ok) { return 0; } eos_static_info("[ %s ] erasing %s %ld => %ld", name.c_str(), it->second.path.c_str(), treeinfo.get_size(), it->second.size); if (::unlink(it->second.path.c_str())) { eos_static_err("[ %s ] failed to unlink file %s errno=%d", name.c_str(), it->second.path.c_str(), errno); } else { treeinfo.change(-it->second.size, -1); } } return 0; } /* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ void /* -------------------------------------------------------------------------- */ dircleaner::leveler(ThreadAssistant& assistant) /* -------------------------------------------------------------------------- */ { size_t n = 0; while (1) { assistant.wait_for(std::chrono::seconds(15)); if (assistant.terminationRequested()) { return; } // check the partition status struct statvfs svfs; int rsfs = statvfs(path.c_str(), &svfs); if (!rsfs) { uint64_t free_partition_bytes = svfs.f_bavail * svfs.f_bsize; uint64_t total_partition_bytes = svfs.f_blocks * svfs.f_frsize; double freep = 100.0 * free_partition_bytes / total_partition_bytes; double filled = 100.0 - freep; eos_static_info("[ %s ] diskspace on partition path %s free-bytes=%lu total-bytes=%lu filled=%.02f %%", name.c_str(), path.c_str(), free_partition_bytes, total_partition_bytes, filled); if (filled > clean_threshold) { // we force a complete cleanup of the cache if disk space runs low eos_static_warning("[ %s ] diskspace on partition path %s less than 5%% free : free-bytes=%lu total-bytes=%lu filled=%.02f %% - cleaning cache", name.c_str(), path.c_str(), free_partition_bytes, total_partition_bytes, filled); cleanall(trim_suffix); } } else { eos_static_err("[ %s ] statvfs on path=%s failed with retc=%d errno=%d", name.c_str(), path.c_str(), rsfs, errno); } std::lock_guard mLock(cleaningMutex); trim(!(n % (1 * 60 * 4))); // forced trim every hour n++; } }