//------------------------------------------------------------------------------ //! @file diskcache.cc //! @author Andreas-Joachim Peters CERN //! @brief data cache disk implementation //------------------------------------------------------------------------------ /************************************************************************ * 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 "diskcache.hh" #include "common/Logging.hh" #include "common/Path.hh" #include #include #include #ifdef __APPLE__ #define EKEYEXPIRED 127 #include "XrdSys/XrdSysPlatform.hh" #endif #include "common/XattrCompat.hh" std::string diskcache::sLocation; bufferllmanager diskcache::sBufferManager; off_t diskcache::sMaxSize = 2 * 1024 * 1024ll; float diskcache::sCleanThreshold = 85.0; shared_ptr diskcache::sDirCleaner; /* -------------------------------------------------------------------------- */ int /* -------------------------------------------------------------------------- */ diskcache::init(const cacheconfig& config) /* -------------------------------------------------------------------------- */ { if (::access(config.location.c_str(), W_OK)) { return errno; } sLocation = config.location; if (config.per_file_cache_max_size) { diskcache::sMaxSize = config.per_file_cache_max_size; } if (config.clean_threshold) { diskcache::sCleanThreshold = config.clean_threshold; } return 0; } /* -------------------------------------------------------------------------- */ int /* -------------------------------------------------------------------------- */ diskcache::init_daemonized(const cacheconfig& config) /* -------------------------------------------------------------------------- */ { if (config.per_file_cache_max_size) { diskcache::sMaxSize = config.per_file_cache_max_size; } sDirCleaner = std::make_shared(config.location, "dc", config.total_file_cache_size, config.total_file_cache_inodes, config.clean_threshold ); sDirCleaner->set_trim_suffix(".dc"); if (config.clean_on_startup) { eos_static_info("cleaning cache path=%s", config.location.c_str()); if (sDirCleaner->cleanall(".dc")) { eos_static_err("cache cleanup failed"); return -1; } } // start the disk cache leveling thread; return 0; } /* -------------------------------------------------------------------------- */ diskcache::diskcache(fuse_ino_t _ino) : ino(_ino), nattached(0), fd(-1) /* -------------------------------------------------------------------------- */ { memset(&attachstat, 0, sizeof(attachstat)); memset(&detachstat, 0, sizeof(detachstat)); return; } /* -------------------------------------------------------------------------- */ diskcache::~diskcache() /* -------------------------------------------------------------------------- */ { if (!nattached) return; // we may be destroyed while still being attached, e.g. during truncation // via datax::truncate -> datax::remove_file_cache -> io::disable_file_cache eos_static_debug("diskcache::~diskcache nattached=%lu fd=%d\n", nattached, fd); if (fd<0) return; if (fstat(fd, &detachstat)) { return; } sDirCleaner->get_external_tree().change(detachstat.st_size - attachstat.st_size, 0); (void) close(fd); } /* -------------------------------------------------------------------------- */ int diskcache::location(std::string& path, bool mkpath) /* -------------------------------------------------------------------------- */ { char cache_path[1024 + 20]; snprintf(cache_path, sizeof(cache_path), "%s/%03lX/%08lX.dc", sLocation.c_str(), (ino > 0x0fffffff)? (ino >> 28) % 4096 : ino %4096, ino); if (mkpath) { eos::common::Path cPath(cache_path); if (!cPath.MakeParentPath(S_IRWXU)) { return -errno; } } path = cache_path; return 0; } /* -------------------------------------------------------------------------- */ int /* -------------------------------------------------------------------------- */ diskcache::attach(fuse_req_t req, std::string& acookie, int flag) /* -------------------------------------------------------------------------- */ { XrdSysMutexHelper lLock(mMutex); int rc = 0; if (nattached == 0) { std::string path; rc = location(path); if (rc) { return rc; } if (stat(path.c_str(), &attachstat)) { // a new file sDirCleaner->get_external_tree().change(0, 1); } // need to open the file size_t tries=0; do { fd = open(path.c_str(), O_CREAT | O_RDWR, S_IRWXU); if (fd < 0) { if (errno == ENOENT) { tries++; // re-create the directory structure rc = location(path); if (rc) { return rc; } if (tries < 10) { continue; } else { return -errno; } } return -errno; } break; } while(1); } std::string ccookie; if ((!nattached) && ((!cookie(ccookie) || (ccookie != "")))) { if (fstat(fd, &attachstat)) { return errno; } // compare if the cookies are identical, otherwise we truncate to 0 if (ccookie != acookie) { eos_static_debug("diskcache::attach truncating for cookie: %s <=> %s\n", ccookie.c_str(), acookie.c_str()); if (truncate(0)) { char msg[1024]; snprintf(msg, sizeof(msg), "failed to truncate to invalidate cache file - ino=%08lx", ino); throw std::runtime_error(msg); } set_cookie(acookie); rc = EKEYEXPIRED; } } else { set_cookie(acookie); } nattached++; return rc; } /* -------------------------------------------------------------------------- */ int /* -------------------------------------------------------------------------- */ diskcache::detach(std::string& cookie) /* -------------------------------------------------------------------------- */ { XrdSysMutexHelper lLock(mMutex); nattached--; if (!nattached) { if (fstat(fd, &detachstat)) { return errno; } sDirCleaner->get_external_tree().change(detachstat.st_size - attachstat.st_size, 0); int rc = close(fd); fd = -1; if (rc) { return errno; } } return 0; } /* -------------------------------------------------------------------------- */ int /* -------------------------------------------------------------------------- */ diskcache::unlink() /* -------------------------------------------------------------------------- */ { std::string path; int rc = location(path); if (!rc) { struct stat buf; rc = stat(path.c_str(), &buf); if (!rc) { rc = ::unlink(path.c_str()); if (!rc) { // a deleted file sDirCleaner->get_external_tree().change(-buf.st_size, -1); } } } return rc; } /* -------------------------------------------------------------------------- */ ssize_t /* -------------------------------------------------------------------------- */ diskcache::pread(void* buf, size_t count, off_t offset) /* -------------------------------------------------------------------------- */ { eos_static_debug("diskcache::pread %lu %lu\n", count, offset); // restrict to our local max size cache size if (offset >= sMaxSize) { return 0; } if ((off_t)(offset + count) > sMaxSize) { count = sMaxSize - offset; } return ::pread(fd, buf, count, offset); } /* -------------------------------------------------------------------------- */ ssize_t diskcache::pwrite(const void* buf, size_t count, off_t offset) /* -------------------------------------------------------------------------- */ { eos_static_debug("diskcache::pwrite %lu %lu\n", count, offset); if ((off_t) offset >= sMaxSize) { return 0; } if ((off_t)(offset + count) > sMaxSize) { count = sMaxSize - offset; } return ::pwrite(fd, buf, count, offset); } /* -------------------------------------------------------------------------- */ int /* -------------------------------------------------------------------------- */ diskcache::truncate(off_t offset) /* -------------------------------------------------------------------------- */ { eos_static_debug("diskcache::truncate %lu\n", offset); if (fstat(fd, &detachstat)) { return -1; } int rc = 0; if (offset >= sMaxSize) { offset = sMaxSize; } rc = ::ftruncate(fd, offset); if (!rc) { sDirCleaner->get_external_tree().change(detachstat.st_size - attachstat.st_size, 0); attachstat.st_size = offset; } return rc; } /* -------------------------------------------------------------------------- */ int /* -------------------------------------------------------------------------- */ diskcache::sync() /* -------------------------------------------------------------------------- */ { return ::fdatasync(fd); } /* -------------------------------------------------------------------------- */ size_t /* -------------------------------------------------------------------------- */ diskcache::size() /* -------------------------------------------------------------------------- */ { struct stat buf; buf.st_size = 0; if (fd > 0) { if (::fstat(fd, &buf)) { throw std::runtime_error("diskcache stat failure"); } return buf.st_size; } else { return 0; } } /* -------------------------------------------------------------------------- */ int /* -------------------------------------------------------------------------- */ diskcache::set_attr(const std::string& key, const std::string& value) /* -------------------------------------------------------------------------- */ { int rc = 0; if (fd > 0) { #ifdef __APPLE__ rc = fsetxattr(fd, key.c_str(), value.c_str(), value.size(), 0, 0); #else rc = fsetxattr(fd, key.c_str(), value.c_str(), value.size(), 0); #endif if (rc && errno == ENOTSUP) { throw std::runtime_error("diskcache has no xattr support"); } } else { // set attribute by path since the diskcache could be unattached std::string path; rc = location(path); #ifdef __APPLE__ rc = setxattr(path.c_str(), key.c_str(), value.c_str(), value.size(), 0, 0); #else rc = setxattr(path.c_str(), key.c_str(), value.c_str(), value.size(), 0); #endif } eos_static_debug("set_attr key=%s val=%s fd=%d rc=%d\n", key.c_str(), value.c_str(), fd, rc); return -1; } /* -------------------------------------------------------------------------- */ int /* -------------------------------------------------------------------------- */ diskcache::attr(const std::string& key, std::string& value) /* -------------------------------------------------------------------------- */ { if (fd > 0) { value.resize(4096); ssize_t n = 0; #ifdef __APPLE__ n = fgetxattr(fd, key.c_str(), (void*) value.c_str(), value.size(), 0, 0); #else n = fgetxattr(fd, key.c_str(), (void*) value.c_str(), value.size()); #endif if (n >= 0) { value.resize(n); return 0; } else { value.resize(0); return -1; } } return -1; } /* -------------------------------------------------------------------------- */ int /* -------------------------------------------------------------------------- */ diskcache::rescue(std::string& rescue_location) /* -------------------------------------------------------------------------- */ { std::string path; int rc = location(path); if (!rescue_location.length()) { rescue_location = path; rescue_location += ".recover"; } if (!rc) { return ::rename(path.c_str(), rescue_location.c_str()); } else { return rc; } } /* -------------------------------------------------------------------------- */ int /* -------------------------------------------------------------------------- */ diskcache::recovery_location(std::string& recovery_location) /* -------------------------------------------------------------------------- */ { std::string path; int rc = location(path); recovery_location = path; recovery_location + ".download"; return rc; }