1//===- OnDiskCommon.cpp ---------------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "OnDiskCommon.h"
10#include "llvm/Support/Errno.h"
11#include "llvm/Support/Error.h"
12#include "llvm/Support/FileSystem.h"
13#include "llvm/Support/Path.h"
14#include "llvm/Support/Process.h"
15#include <mutex>
16#include <thread>
17
18#if __has_include(<sys/file.h>)
19#include <sys/file.h>
20#ifdef LOCK_SH
21#define HAVE_FLOCK 1
22#else
23#define HAVE_FLOCK 0
24#endif
25#endif
26
27#if __has_include(<fcntl.h>)
28#include <fcntl.h>
29#endif
30
31#if __has_include(<sys/mount.h>)
32#include <sys/mount.h> // statfs
33#endif
34
35#ifdef __APPLE__
36#if __has_include(<sys/sysctl.h>)
37#include <sys/sysctl.h>
38#endif
39#endif
40
41using namespace llvm;
42
43static uint64_t OnDiskCASMaxMappingSize = 0;
44
45Expected<std::optional<uint64_t>> cas::ondisk::getOverriddenMaxMappingSize() {
46 static std::once_flag Flag;
47 Error Err = Error::success();
48 std::call_once(once&: Flag, f: [&Err] {
49 ErrorAsOutParameter EAO(&Err);
50 constexpr const char *EnvVar = "LLVM_CAS_MAX_MAPPING_SIZE";
51 auto Value = sys::Process::GetEnv(name: EnvVar);
52 if (!Value)
53 return;
54
55 uint64_t Size;
56 if (StringRef(*Value).getAsInteger(/*auto*/ Radix: 0, Result&: Size))
57 Err = createStringError(EC: inconvertibleErrorCode(),
58 Fmt: "invalid value for %s: expected integer", Vals: EnvVar);
59 OnDiskCASMaxMappingSize = Size;
60 });
61
62 if (Err)
63 return std::move(Err);
64
65 if (OnDiskCASMaxMappingSize == 0)
66 return std::nullopt;
67
68 return OnDiskCASMaxMappingSize;
69}
70
71void cas::ondisk::setMaxMappingSize(uint64_t Size) {
72 OnDiskCASMaxMappingSize = Size;
73}
74
75std::error_code cas::ondisk::lockFileThreadSafe(int FD,
76 sys::fs::LockKind Kind) {
77#if HAVE_FLOCK
78 if (sys::RetryAfterSignal(
79 Fail: -1, F&: flock, As: FD,
80 As: Kind == sys::fs::LockKind::Exclusive ? LOCK_EX : LOCK_SH) == 0)
81 return std::error_code();
82 return std::error_code(errno, std::generic_category());
83#elif defined(_WIN32)
84 // On Windows this implementation is thread-safe.
85 return sys::fs::lockFile(FD, Kind);
86#else
87 return make_error_code(std::errc::no_lock_available);
88#endif
89}
90
91std::error_code cas::ondisk::unlockFileThreadSafe(int FD) {
92#if HAVE_FLOCK
93 if (sys::RetryAfterSignal(Fail: -1, F&: flock, As: FD, LOCK_UN) == 0)
94 return std::error_code();
95 return std::error_code(errno, std::generic_category());
96#elif defined(_WIN32)
97 // On Windows this implementation is thread-safe.
98 return sys::fs::unlockFile(FD);
99#else
100 return make_error_code(std::errc::no_lock_available);
101#endif
102}
103
104std::error_code
105cas::ondisk::tryLockFileThreadSafe(int FD, std::chrono::milliseconds Timeout,
106 sys::fs::LockKind Kind) {
107#if HAVE_FLOCK
108 auto Start = std::chrono::steady_clock::now();
109 auto End = Start + Timeout;
110 do {
111 if (sys::RetryAfterSignal(
112 Fail: -1, F&: flock, As: FD,
113 As: (Kind == sys::fs::LockKind::Exclusive ? LOCK_EX : LOCK_SH) |
114 LOCK_NB) == 0)
115 return std::error_code();
116 int Error = errno;
117 if (Error == EWOULDBLOCK) {
118 if (Timeout.count() == 0)
119 break;
120 // Match sys::fs::tryLockFile, which sleeps for 1 ms per attempt.
121 std::this_thread::sleep_for(rtime: std::chrono::milliseconds(1));
122 continue;
123 }
124 return std::error_code(Error, std::generic_category());
125 } while (std::chrono::steady_clock::now() < End);
126 return make_error_code(e: std::errc::no_lock_available);
127#elif defined(_WIN32)
128 // On Windows this implementation is thread-safe.
129 return sys::fs::tryLockFile(FD, Timeout, Kind);
130#else
131 return make_error_code(std::errc::no_lock_available);
132#endif
133}
134
135Expected<size_t> cas::ondisk::preallocateFileTail(int FD, size_t CurrentSize,
136 size_t NewSize) {
137 auto CreateError = [&](std::error_code EC) -> Expected<size_t> {
138 if (EC == std::errc::not_supported)
139 // Ignore ENOTSUP in case the filesystem cannot preallocate.
140 return NewSize;
141#if defined(HAVE_POSIX_FALLOCATE)
142 if (EC == std::errc::invalid_argument && CurrentSize < NewSize && // len > 0
143 NewSize < std::numeric_limits<off_t>::max()) // 0 <= offset, len < max
144 // Prior to 2024, POSIX required EINVAL for cases that should be ENOTSUP,
145 // so handle it the same as above if it is not one of the other ways to
146 // get EINVAL.
147 return NewSize;
148#endif
149 return createStringError(EC,
150 S: "failed to allocate to CAS file: " + EC.message());
151 };
152#if defined(HAVE_POSIX_FALLOCATE)
153 // Note: posix_fallocate returns its error directly, not via errno.
154 int Err;
155 do {
156 Err = posix_fallocate(FD, CurrentSize, NewSize - CurrentSize);
157 } while (Err == EINTR);
158 if (Err)
159 return CreateError(std::error_code(Err, std::generic_category()));
160 return NewSize;
161#elif defined(__APPLE__)
162 fstore_t FAlloc;
163 FAlloc.fst_flags = F_ALLOCATEALL;
164#if defined(F_ALLOCATEPERSIST) && \
165 defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \
166 __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 130000
167 // F_ALLOCATEPERSIST is introduced in macOS 13.
168 FAlloc.fst_flags |= F_ALLOCATEPERSIST;
169#endif
170 FAlloc.fst_posmode = F_PEOFPOSMODE;
171 FAlloc.fst_offset = 0;
172 FAlloc.fst_length = NewSize - CurrentSize;
173 FAlloc.fst_bytesalloc = 0;
174 if (sys::RetryAfterSignal(-1, ::fcntl, FD, F_PREALLOCATE, &FAlloc) == -1)
175 return CreateError(errnoAsErrorCode());
176 assert(CurrentSize + FAlloc.fst_bytesalloc >= NewSize);
177 return CurrentSize + FAlloc.fst_bytesalloc;
178#else
179 (void)CreateError; // Silence unused variable.
180 return NewSize; // Pretend it worked.
181#endif
182}
183
184bool cas::ondisk::useSmallMappingSize(const Twine &P) {
185 // Add exceptions to use small database file here.
186#if defined(__APPLE__) && __has_include(<sys/mount.h>)
187 // macOS tmpfs does not support sparse tails.
188 SmallString<128> PathStorage;
189 StringRef Path = P.toNullTerminatedStringRef(PathStorage);
190 struct statfs StatFS;
191 if (statfs(Path.data(), &StatFS) != 0)
192 return false;
193
194 if (strcmp(StatFS.f_fstypename, "tmpfs") == 0)
195 return true;
196#endif
197 // Default to use regular database file.
198 return false;
199}
200
201Expected<uint64_t> cas::ondisk::getBootTime() {
202#ifdef __APPLE__
203#if __has_include(<sys/sysctl.h>) && defined(KERN_BOOTTIME)
204 struct timeval TV;
205 size_t TVLen = sizeof(TV);
206 int KernBoot[2] = {CTL_KERN, KERN_BOOTTIME};
207 if (sysctl(KernBoot, 2, &TV, &TVLen, nullptr, 0) < 0)
208 return createStringError(llvm::errnoAsErrorCode(),
209 "failed to get boottime");
210 if (TVLen != sizeof(TV))
211 return createStringError("sysctl kern.boottime unexpected format");
212 return TV.tv_sec;
213#else
214 return 0;
215#endif
216#elif defined(__linux__)
217 // Use the mtime for /proc, which is recreated during system boot.
218 // We could also read /proc/stat and search for 'btime'.
219 sys::fs::file_status Status;
220 if (std::error_code EC = sys::fs::status(path: "/proc", result&: Status))
221 return createFileError(F: "/proc", EC);
222 return Status.getLastModificationTime().time_since_epoch().count();
223#else
224 return 0;
225#endif
226}
227
228Expected<StringRef>
229cas::ondisk::UniqueTempFile::createAndCopyFrom(StringRef ParentPath,
230 StringRef CopyFromPath) {
231 // \c clonefile requires that the destination path doesn't exist. We create
232 // a "placeholder" temporary file, then modify its path a bit and use that
233 // for \c clonefile to write to.
234 // FIXME: Instead of creating a dummy file, add a new file system API for
235 // copying to a unique path that can loop while checking EEXIST.
236 SmallString<256> UniqueTmpPath;
237 SmallString<256> Model;
238 Model += ParentPath;
239 sys::path::append(path&: Model, a: "%%%%%%%.tmp");
240 if (std::error_code EC = sys::fs::createUniqueFile(Model, ResultPath&: UniqueTmpPath))
241 return createFileError(F: Model, EC);
242 TmpPath = std::move(UniqueTmpPath);
243 TmpPath += ".tmp"; // modify so that there's no file at that path.
244 // \c copy_file will use \c clonefile when applicable.
245 if (std::error_code EC = sys::fs::copy_file(From: CopyFromPath, To: TmpPath))
246 return createFileError(F: TmpPath, EC);
247
248 return TmpPath;
249}
250
251Error cas::ondisk::UniqueTempFile::renameTo(StringRef RenameToPath) {
252 if (std::error_code EC = sys::fs::rename(from: TmpPath, to: RenameToPath))
253 return createFileError(F: RenameToPath, EC);
254 TmpPath.clear();
255 return Error::success();
256}
257
258cas::ondisk::UniqueTempFile::~UniqueTempFile() {
259 if (!TmpPath.empty())
260 sys::fs::remove(path: TmpPath);
261 if (!UniqueTmpPath.empty())
262 sys::fs::remove(path: UniqueTmpPath);
263}
264