From: Joel Rosdahl Date: Mon, 23 Jan 2023 21:14:54 +0000 (+0100) Subject: fix: Disable inode cache if filesystem risks getting full soon X-Git-Tag: v4.8~41 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=de63795267aba8ba91478cc761d12aa36d530619;p=thirdparty%2Fccache.git fix: Disable inode cache if filesystem risks getting full soon Some filesystems, for instance btrfs with compression enabled, apparently make a posix_fallocate call succeed without actually allocating the requested space for the file. This means that if the file is mapped into memory, like done by the inode cache, the process can crash when accessing the memory if the filesystem is full. This commit implements a workaround: the inode cache is disabled if the filesystem reports that it has less than 100 MiB free space. The free space check is valid for one second before it is done again. This should hopefully make crashes very rare in practice. Closes #1236. --- diff --git a/src/InodeCache.cpp b/src/InodeCache.cpp index e6245fa67..17e924177 100644 --- a/src/InodeCache.cpp +++ b/src/InodeCache.cpp @@ -20,7 +20,6 @@ #include "Config.hpp" #include "Digest.hpp" -#include "Fd.hpp" #include "Finalizer.hpp" #include "Hash.hpp" #include "Logging.hpp" @@ -29,8 +28,6 @@ #include "Util.hpp" #include "fmtmacros.hpp" -#include - #include #include #include @@ -78,6 +75,14 @@ const uint32_t k_num_entries = 4; // Maximum time the spin lock loop will try before giving up. const auto k_max_lock_duration = util::Duration(5); +// The memory-mapped file may reside on a filesystem with compression. Memory +// accesses to the file risk crashing if such a filesystem gets full, so stop +// using the inode cache well before this happens. +const uint64_t k_min_fs_mib_left = 100; // 100 MiB + +// How long a filesystem space check is valid before we make a new one. +const util::Duration k_fs_space_check_valid_duration(1); + static_assert(Digest::size() == 20, "Increment version number if size of digest is changed."); static_assert(std::is_trivially_copyable::value, @@ -229,17 +234,21 @@ InodeCache::mmap_file(const std::string& inode_cache_file) munmap(m_sr, sizeof(SharedRegion)); m_sr = nullptr; } - Fd fd(open(inode_cache_file.c_str(), O_RDWR)); - if (!fd) { + m_fd = Fd(open(inode_cache_file.c_str(), O_RDWR)); + if (!m_fd) { LOG("Failed to open inode cache {}: {}", inode_cache_file, strerror(errno)); return false; } - if (!fd_is_on_known_to_work_file_system(*fd)) { + if (!fd_is_on_known_to_work_file_system(*m_fd)) { return false; } - SharedRegion* sr = reinterpret_cast(mmap( - nullptr, sizeof(SharedRegion), PROT_READ | PROT_WRITE, MAP_SHARED, *fd, 0)); - fd.close(); + SharedRegion* sr = + reinterpret_cast(mmap(nullptr, + sizeof(SharedRegion), + PROT_READ | PROT_WRITE, + MAP_SHARED, + *m_fd, + 0)); if (sr == MMAP_FAILED) { LOG("Failed to mmap {}: {}", inode_cache_file, strerror(errno)); return false; @@ -387,6 +396,24 @@ InodeCache::initialize() return false; } + if (m_fd) { + auto now = util::TimePoint::now(); + if (now > m_last_fs_space_check + k_fs_space_check_valid_duration) { + m_last_fs_space_check = now; + + struct statfs buf; + if (fstatfs(*m_fd, &buf) != 0) { + LOG("fstatfs failed: {}", strerror(errno)); + return false; + } + if (buf.f_bavail * 512 < k_min_fs_mib_left * 1024 * 1024) { + LOG("Filesystem has less than {} MiB free space, not using inode cache", + k_min_fs_mib_left); + return false; + } + } + } + if (m_sr) { return true; } diff --git a/src/InodeCache.hpp b/src/InodeCache.hpp index c4e8c4eab..5819ed070 100644 --- a/src/InodeCache.hpp +++ b/src/InodeCache.hpp @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2022 Joel Rosdahl and other contributors +// Copyright (C) 2020-2023 Joel Rosdahl and other contributors // // See doc/AUTHORS.adoc for a complete list of contributors. // @@ -18,7 +18,9 @@ #pragma once +#include #include +#include #include #include @@ -130,7 +132,9 @@ private: const Config& m_config; util::Duration m_min_age; + Fd m_fd; struct SharedRegion* m_sr = nullptr; bool m_failed = false; const pid_t m_self_pid; + util::TimePoint m_last_fs_space_check; };