1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
|
//===- 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/ADT/StringRef.h"
#include "llvm/Config/config.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/Process.h"
#include <limits>
#include <mutex>
#include <optional>
#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
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, bool Exclusive) {
#if HAVE_FLOCK
if (flock(FD, 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, Exclusive);
#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,
bool Exclusive) {
#if HAVE_FLOCK
auto Start = std::chrono::steady_clock::now();
auto End = Start + Timeout;
do {
if (flock(FD, (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, Exclusive);
#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 | F_ALLOCATEPERSIST;
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
return NewSize; // Pretend it worked.
#endif
}
|