llvm-project/llvm/lib/CAS/OnDiskCommon.cpp
Steven Wu a38f847187
[CAS] Fix deprecation warning in getBootTime (#171168)
For some older Linux distro that still ships deprecated sysctl header,
there can be deprecation warnings when building LLVMCAS. This also
results LLVMCAS to use the deprecated sysctl function, while it is only
intended to be used on Darwin platforms.

Fix the issue by only including sysctl on Apple platforms. Also move the
platform dependent `getBootTime` code into OnDiskCommon.cpp.
2025-12-08 11:20:36 -08:00

215 lines
6.9 KiB
C++

//===- OnDiskCommon.cpp ---------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "OnDiskCommon.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Process.h"
#include <mutex>
#include <thread>
#if __has_include(<sys/file.h>)
#include <sys/file.h>
#ifdef LOCK_SH
#define HAVE_FLOCK 1
#else
#define HAVE_FLOCK 0
#endif
#endif
#if __has_include(<fcntl.h>)
#include <fcntl.h>
#endif
#if __has_include(<sys/mount.h>)
#include <sys/mount.h> // statfs
#endif
#ifdef __APPLE__
#if __has_include(<sys/sysctl.h>)
#include <sys/sysctl.h>
#endif
#endif
using namespace llvm;
static uint64_t OnDiskCASMaxMappingSize = 0;
Expected<std::optional<uint64_t>> cas::ondisk::getOverriddenMaxMappingSize() {
static std::once_flag Flag;
Error Err = Error::success();
std::call_once(Flag, [&Err] {
ErrorAsOutParameter EAO(&Err);
constexpr const char *EnvVar = "LLVM_CAS_MAX_MAPPING_SIZE";
auto Value = sys::Process::GetEnv(EnvVar);
if (!Value)
return;
uint64_t Size;
if (StringRef(*Value).getAsInteger(/*auto*/ 0, Size))
Err = createStringError(inconvertibleErrorCode(),
"invalid value for %s: expected integer", EnvVar);
OnDiskCASMaxMappingSize = Size;
});
if (Err)
return std::move(Err);
if (OnDiskCASMaxMappingSize == 0)
return std::nullopt;
return OnDiskCASMaxMappingSize;
}
void cas::ondisk::setMaxMappingSize(uint64_t Size) {
OnDiskCASMaxMappingSize = Size;
}
std::error_code cas::ondisk::lockFileThreadSafe(int FD,
sys::fs::LockKind Kind) {
#if HAVE_FLOCK
if (flock(FD, Kind == sys::fs::LockKind::Exclusive ? LOCK_EX : LOCK_SH) == 0)
return std::error_code();
return std::error_code(errno, std::generic_category());
#elif defined(_WIN32)
// On Windows this implementation is thread-safe.
return sys::fs::lockFile(FD, Kind);
#else
return make_error_code(std::errc::no_lock_available);
#endif
}
std::error_code cas::ondisk::unlockFileThreadSafe(int FD) {
#if HAVE_FLOCK
if (flock(FD, LOCK_UN) == 0)
return std::error_code();
return std::error_code(errno, std::generic_category());
#elif defined(_WIN32)
// On Windows this implementation is thread-safe.
return sys::fs::unlockFile(FD);
#else
return make_error_code(std::errc::no_lock_available);
#endif
}
std::error_code
cas::ondisk::tryLockFileThreadSafe(int FD, std::chrono::milliseconds Timeout,
sys::fs::LockKind Kind) {
#if HAVE_FLOCK
auto Start = std::chrono::steady_clock::now();
auto End = Start + Timeout;
do {
if (flock(FD, (Kind == sys::fs::LockKind::Exclusive ? LOCK_EX : LOCK_SH) |
LOCK_NB) == 0)
return std::error_code();
int Error = errno;
if (Error == EWOULDBLOCK) {
// Match sys::fs::tryLockFile, which sleeps for 1 ms per attempt.
std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
}
return std::error_code(Error, std::generic_category());
} while (std::chrono::steady_clock::now() < End);
return make_error_code(std::errc::no_lock_available);
#elif defined(_WIN32)
// On Windows this implementation is thread-safe.
return sys::fs::tryLockFile(FD, Timeout, Kind);
#else
return make_error_code(std::errc::no_lock_available);
#endif
}
Expected<size_t> cas::ondisk::preallocateFileTail(int FD, size_t CurrentSize,
size_t NewSize) {
auto CreateError = [&](std::error_code EC) -> Expected<size_t> {
if (EC == std::errc::not_supported)
// Ignore ENOTSUP in case the filesystem cannot preallocate.
return NewSize;
#if defined(HAVE_POSIX_FALLOCATE)
if (EC == std::errc::invalid_argument && CurrentSize < NewSize && // len > 0
NewSize < std::numeric_limits<off_t>::max()) // 0 <= offset, len < max
// Prior to 2024, POSIX required EINVAL for cases that should be ENOTSUP,
// so handle it the same as above if it is not one of the other ways to
// get EINVAL.
return NewSize;
#endif
return createStringError(EC,
"failed to allocate to CAS file: " + EC.message());
};
#if defined(HAVE_POSIX_FALLOCATE)
// Note: posix_fallocate returns its error directly, not via errno.
if (int Err = posix_fallocate(FD, CurrentSize, NewSize - CurrentSize))
return CreateError(std::error_code(Err, std::generic_category()));
return NewSize;
#elif defined(__APPLE__)
fstore_t FAlloc;
FAlloc.fst_flags = F_ALLOCATEALL;
#if defined(F_ALLOCATEPERSIST) && \
defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \
__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 130000
// F_ALLOCATEPERSIST is introduced in macOS 13.
FAlloc.fst_flags |= F_ALLOCATEPERSIST;
#endif
FAlloc.fst_posmode = F_PEOFPOSMODE;
FAlloc.fst_offset = 0;
FAlloc.fst_length = NewSize - CurrentSize;
FAlloc.fst_bytesalloc = 0;
if (fcntl(FD, F_PREALLOCATE, &FAlloc))
return CreateError(errnoAsErrorCode());
assert(CurrentSize + FAlloc.fst_bytesalloc >= NewSize);
return CurrentSize + FAlloc.fst_bytesalloc;
#else
(void)CreateError; // Silence unused variable.
return NewSize; // Pretend it worked.
#endif
}
bool cas::ondisk::useSmallMappingSize(const Twine &P) {
// Add exceptions to use small database file here.
#if defined(__APPLE__) && __has_include(<sys/mount.h>)
// macOS tmpfs does not support sparse tails.
SmallString<128> PathStorage;
StringRef Path = P.toNullTerminatedStringRef(PathStorage);
struct statfs StatFS;
if (statfs(Path.data(), &StatFS) != 0)
return false;
if (strcmp(StatFS.f_fstypename, "tmpfs") == 0)
return true;
#endif
// Default to use regular database file.
return false;
}
Expected<uint64_t> cas::ondisk::getBootTime() {
#ifdef __APPLE__
#if __has_include(<sys/sysctl.h>) && defined(KERN_BOOTTIME)
struct timeval TV;
size_t TVLen = sizeof(TV);
int KernBoot[2] = {CTL_KERN, KERN_BOOTTIME};
if (sysctl(KernBoot, 2, &TV, &TVLen, nullptr, 0) < 0)
return createStringError(llvm::errnoAsErrorCode(),
"failed to get boottime");
if (TVLen != sizeof(TV))
return createStringError("sysctl kern.boottime unexpected format");
return TV.tv_sec;
#else
return 0;
#endif
#elif defined(__linux__)
// Use the mtime for /proc, which is recreated during system boot.
// We could also read /proc/stat and search for 'btime'.
sys::fs::file_status Status;
if (std::error_code EC = sys::fs::status("/proc", Status))
return createFileError("/proc", EC);
return Status.getLastModificationTime().time_since_epoch().count();
#else
return 0;
#endif
}