/* * Phusion Passenger - https://www.phusionpassenger.com/ * Copyright (c) 2010-2018 Phusion Holding B.V. * * "Passenger", "Phusion Passenger" and "Union Station" are registered * trademarks of Phusion Holding B.V. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifndef _PASSENGER_CACHED_FILE_STAT_HPP_ #define _PASSENGER_CACHED_FILE_STAT_HPP_ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Passenger { using namespace std; using namespace oxt; using namespace boost; /** * CachedFileStat allows one to stat() files at a throttled rate, in order * to minimize stress on the filesystem. It does this by caching the old stat * data for a specified amount of time. * * The cache has a maximum size, which may be altered during runtime. If a * file that wasn't in the cache is being stat()ed, and the cache is full, * then the oldest cache entry will be removed. */ class CachedFileStat { public: /** Represents a cached file stat entry. */ class Entry { private: /** The last return value of stat(). */ int last_result; /** The errno set by the last stat() call. */ int last_errno; /** The last time a stat() was performed. */ time_t last_time; /** * Checks whether `interval` seconds have elapsed since `begin`. * The current time is returned via the `currentTime` argument, * so that the caller doesn't have to call time() again if it needs the current * time. * * @pre begin <= time(NULL) * @return Whether `interval` seconds have elapsed since `begin`. * @throws TimeRetrievalException Something went wrong while retrieving the time. * @throws boost::thread_interrupted */ bool expired(time_t begin, unsigned int interval, time_t ¤tTime) { currentTime = SystemTime::get(); return (unsigned int) (currentTime - begin) >= interval; } public: /** The cached stat info. */ struct stat info; /** This entry's filename. */ string filename; /** * Creates a new Entry object. The file will not be * stat()ted until you call refresh(). * * @param filename The file to stat. */ Entry(const string &_filename) : filename(_filename) { memset(&info, 0, sizeof(struct stat)); last_result = -1; last_errno = 0; last_time = 0; } /** * Re-stat() the file, if necessary. If throttleRate seconds have * passed since the last time stat() was called, then the file will be * re-stat()ted. * * The stat information, which may either be the result of a new stat() call * or just the old cached information, is be available in the info * member. * * @return 0 if the stat() call succeeded or if no stat() was performed, * -1 if something went wrong while statting the file. In the latter * case, errno will be populated with an appropriate error code. * @throws TimeRetrievalException Something went wrong while retrieving the * system time. * @throws boost::thread_interrupted */ int refresh(unsigned int throttleRate) { time_t currentTime; if (expired(last_time, throttleRate, currentTime)) { last_result = syscalls::stat(filename.c_str(), &info); last_errno = errno; last_time = currentTime; return last_result; } else { errno = last_errno; return last_result; } } }; typedef boost::shared_ptr EntryPtr; typedef list EntryList; typedef StringMap EntryMap; unsigned int maxSize; EntryList entries; EntryMap cache; /** * Creates a new CachedFileStat object. * * @param maxSize The maximum cache size. A size of 0 means unlimited. */ CachedFileStat(unsigned int maxSize = 0) { this->maxSize = maxSize; } /** * Stats the given file. If `throttleRate` seconds have passed since * the last time stat() was called on this file, then the file will be * re-stat()ted, otherwise the cached stat information will be returned. * * @param filename The file to stat. * @param stat A pointer to a stat struct; the retrieved stat information * will be stored here. * @param throttleRate Tells this CachedFileStat that the file may only * be statted at most every throttleRate seconds. * @return 0 if the stat() call succeeded or if the cached stat information was used; * -1 if something went wrong while statting the file. In the latter * case, errno will be populated with an appropriate error code. * @throws SystemException Something went wrong while retrieving the * system time. stat() errors will not result in * SystemException being thrown. * @throws boost::thread_interrupted */ int stat(const StaticString &filename, struct stat *buf, unsigned int throttleRate = 0) { EntryList::iterator it(cache.get(filename, entries.end())); EntryPtr entry; int ret; if (it == entries.end()) { // Filename not in cache. // If cache is full, remove the least recently used // cache entry. if (maxSize != 0 && cache.size() == maxSize) { EntryList::iterator listEnd(entries.end()); listEnd--; string filename2((*listEnd)->filename); entries.pop_back(); cache.remove(filename2); } // Add to cache as most recently used. entry = boost::make_shared(filename); entries.push_front(entry); cache.set(filename, entries.begin()); } else { // Cache hit. entry = *it; // Mark this cache item as most recently used. entries.splice(entries.begin(), entries, it); cache.set(filename, entries.begin()); } ret = entry->refresh(throttleRate); *buf = entry->info; return ret; } /** * Change the maximum size of the cache. If the new size is larger * than the old size, then the oldest entries in the cache are * removed. * * A size of 0 means unlimited. */ void setMaxSize(unsigned int maxSize) { if (maxSize != 0) { int toRemove = cache.size() - maxSize; for (int i = 0; i < toRemove; i++) { string filename(entries.back()->filename); entries.pop_back(); cache.remove(filename); } } this->maxSize = maxSize; } /** * Returns whether `filename` is in the cache. */ bool knows(const StaticString &filename) const { return cache.has(filename); } }; } // namespace Passenger #endif /* _PASSENGER_CACHED_FILE_STAT_HPP_ */