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