Skip to content

Commit 177af2e

Browse files
authored
Merge pull request #10921 from swiftlang/enospc-apfs-6.2
[🍒6.2][llvm][cas] Prevent corruption on ENOSPC on sparse filesystems
2 parents 942eaac + fb8f519 commit 177af2e

File tree

15 files changed

+167
-47
lines changed

15 files changed

+167
-47
lines changed

clang/test/CAS/depscan-cas-log.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010
// RUN: -cc1-args -cc1 -triple x86_64-apple-macosx11.0.0 -emit-obj %s -o %t/t.o -fcas-path %t/cas
1111
// RUN: FileCheck %s --input-file %t/cas/v1.log
1212

13-
// CHECK: [[PID1:[0-9]*]] {{[0-9]*}}: mmap '{{.*}}v8.index'
13+
// CHECK: [[PID1:[0-9]*]] {{[0-9]*}}: mmap '{{.*}}v9.index'
1414
// CHECK: [[PID1]] {{[0-9]*}}: create subtrie
1515

16-
// CHECK: [[PID2:[0-9]*]] {{[0-9]*}}: mmap '{{.*}}v8.index'
16+
// CHECK: [[PID2:[0-9]*]] {{[0-9]*}}: mmap '{{.*}}v9.index'
1717
// Even a minimal compilation involves at least 9 records for the cache key.
1818
// CHECK-COUNT-9: [[PID2]] {{[0-9]*}}: create record
1919

20-
// CHECK: [[PID1]] {{[0-9]*}}: close mmap '{{.*}}v8.index'
20+
// CHECK: [[PID1]] {{[0-9]*}}: close mmap '{{.*}}v9.index'

clang/test/CAS/validate-once.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// RUN: rm -rf %t
22

33
// RUN: llvm-cas --cas %t/cas --ingest %s
4-
// RUN: mv %t/cas/v1.1/v8.data %t/cas/v1.1/v8.data.bak
4+
// RUN: mv %t/cas/v1.1/v9.data %t/cas/v1.1/v9.data.bak
55

66
// RUN: %clang -cc1depscand -execute %{clang-daemon-dir}/%basename_t -cas-args -fcas-path %t/cas -- \
77
// RUN: %clang -target x86_64-apple-macos11 -I %S/Inputs \

llvm/cmake/config-ix.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ check_symbol_exists(malloc_zone_statistics malloc/malloc.h
298298
HAVE_MALLOC_ZONE_STATISTICS)
299299
check_symbol_exists(getrlimit "sys/types.h;sys/time.h;sys/resource.h" HAVE_GETRLIMIT)
300300
check_symbol_exists(posix_spawn spawn.h HAVE_POSIX_SPAWN)
301+
check_symbol_exists(posix_fallocate fcntl.h HAVE_POSIX_FALLOCATE)
301302
check_symbol_exists(pread unistd.h HAVE_PREAD)
302303
check_symbol_exists(sbrk unistd.h HAVE_SBRK)
303304
check_symbol_exists(strerror_r string.h HAVE_STRERROR_R)

llvm/include/llvm/CAS/MappedFileRegionBumpPtr.h

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class MappedFileRegionBumpPtr {
7878
Expected<int64_t> allocateOffset(uint64_t AllocSize);
7979

8080
char *data() const { return Region.data(); }
81-
uint64_t size() const { return *BumpPtr; }
81+
uint64_t size() const { return H->BumpPtr; }
8282
uint64_t capacity() const { return Region.size(); }
8383

8484
RegionT &getRegion() { return Region; }
@@ -100,16 +100,20 @@ class MappedFileRegionBumpPtr {
100100
void destroyImpl();
101101
void moveImpl(MappedFileRegionBumpPtr &RHS) {
102102
std::swap(Region, RHS.Region);
103-
std::swap(BumpPtr, RHS.BumpPtr);
103+
std::swap(H, RHS.H);
104104
std::swap(Path, RHS.Path);
105105
std::swap(FD, RHS.FD);
106106
std::swap(SharedLockFD, RHS.SharedLockFD);
107107
std::swap(Logger, RHS.Logger);
108108
}
109109

110110
private:
111+
struct Header {
112+
std::atomic<int64_t> BumpPtr;
113+
std::atomic<int64_t> AllocatedSize;
114+
};
111115
RegionT Region;
112-
std::atomic<int64_t> *BumpPtr = nullptr;
116+
Header *H = nullptr;
113117
std::string Path;
114118
std::optional<int> FD;
115119
std::optional<int> SharedLockFD;

llvm/include/llvm/Config/config.h.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@
149149
/* Define to 1 if you have the `posix_spawn' function. */
150150
#cmakedefine HAVE_POSIX_SPAWN ${HAVE_POSIX_SPAWN}
151151

152+
/* Define to 1 if you have the `posix_fallocate' function. */
153+
#cmakedefine HAVE_POSIX_FALLOCATE ${HAVE_POSIX_FALLOCATE}
154+
152155
/* Define to 1 if you have the `pread' function. */
153156
#cmakedefine HAVE_PREAD ${HAVE_PREAD}
154157

llvm/lib/CAS/MappedFileRegionBumpPtr.cpp

Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,19 @@
5454
#include "llvm/CAS/MappedFileRegionBumpPtr.h"
5555
#include "OnDiskCommon.h"
5656
#include "llvm/CAS/OnDiskCASLogger.h"
57+
#include "llvm/Support/Compiler.h"
58+
59+
#if LLVM_ON_UNIX
60+
#include <sys/stat.h>
61+
#if __has_include(<sys/param.h>)
62+
#include <sys/param.h>
63+
#endif
64+
#ifdef DEV_BSIZE
65+
#define MAPPED_FILE_BSIZE DEV_BSIZE
66+
#elif __linux__
67+
#define MAPPED_FILE_BSIZE 512
68+
#endif
69+
#endif
5770

5871
using namespace llvm;
5972
using namespace llvm::cas;
@@ -85,6 +98,13 @@ struct FileLockRAII {
8598
return Error::success();
8699
}
87100
};
101+
102+
struct FileSizeInfo {
103+
uint64_t Size;
104+
uint64_t AllocatedSize;
105+
106+
static ErrorOr<FileSizeInfo> get(sys::fs::file_t File);
107+
};
88108
} // end anonymous namespace
89109

90110
Expected<MappedFileRegionBumpPtr> MappedFileRegionBumpPtr::create(
@@ -123,39 +143,41 @@ Expected<MappedFileRegionBumpPtr> MappedFileRegionBumpPtr::create(
123143
return std::move(E);
124144

125145
sys::fs::file_t File = sys::fs::convertFDToNativeFile(FD);
126-
sys::fs::file_status Status;
127-
if (std::error_code EC = sys::fs::status(File, Status))
128-
return createFileError(Result.Path, EC);
146+
auto FileSize = FileSizeInfo::get(File);
147+
if (!FileSize)
148+
return createFileError(Result.Path, FileSize.getError());
129149

130-
if (Status.getSize() < Capacity) {
150+
if (FileSize->Size < Capacity) {
131151
// Lock the file exclusively so only one process will do the initialization.
132152
if (Error E = InitLock.unlock())
133153
return std::move(E);
134154
if (Error E = InitLock.lock(FileLockRAII::Exclusive))
135155
return std::move(E);
136156
// Retrieve the current size now that we have exclusive access.
137-
if (std::error_code EC = sys::fs::status(File, Status))
138-
return createFileError(Result.Path, EC);
157+
FileSize = FileSizeInfo::get(File);
158+
if (!FileSize)
159+
return createFileError(Result.Path, FileSize.getError());
139160
}
140161

141162
// At this point either the file is still under-sized, or we have the size for
142163
// the completely initialized file.
143164

144-
if (Status.getSize() < Capacity) {
165+
if (FileSize->Size < Capacity) {
145166
// We are initializing the file; it may be empty, or may have been shrunk
146167
// during a previous close.
147168
// FIXME: Detect a case where someone opened it with a smaller capacity.
148169
// FIXME: On Windows we should use FSCTL_SET_SPARSE and FSCTL_SET_ZERO_DATA
149170
// to make this a sparse region, if supported.
171+
assert(InitLock.Locked == FileLockRAII::Exclusive);
150172
if (std::error_code EC = sys::fs::resize_file(FD, Capacity))
151173
return createFileError(Result.Path, EC);
152174

153175
if (Result.Logger)
154176
Result.Logger->log_MappedFileRegionBumpPtr_resizeFile(
155-
Result.Path, Status.getSize(), Capacity);
177+
Result.Path, FileSize->Size, Capacity);
156178
} else {
157179
// Someone else initialized it.
158-
Capacity = Status.getSize();
180+
Capacity = FileSize->Size;
159181
}
160182

161183
// Create the mapped region.
@@ -168,14 +190,25 @@ Expected<MappedFileRegionBumpPtr> MappedFileRegionBumpPtr::create(
168190
Result.Region = std::move(Map);
169191
}
170192

171-
if (Status.getSize() == 0) {
193+
if (FileSize->Size == 0) {
194+
assert(InitLock.Locked == FileLockRAII::Exclusive);
172195
// We are creating a new file; run the constructor.
173196
if (Error E = NewFileConstructor(Result))
174197
return std::move(E);
175198
} else {
176199
Result.initializeBumpPtr(BumpPtrOffset);
177200
}
178201

202+
if (FileSize->Size < Capacity && FileSize->AllocatedSize < Capacity) {
203+
// We are initializing the file; sync the allocated size in case it
204+
// changed when truncating or during construction.
205+
FileSize = FileSizeInfo::get(File);
206+
if (!FileSize)
207+
return createFileError(Result.Path, FileSize.getError());
208+
assert(InitLock.Locked == FileLockRAII::Exclusive);
209+
Result.H->AllocatedSize.exchange(FileSize->AllocatedSize);
210+
}
211+
179212
return Result;
180213
}
181214

@@ -189,7 +222,7 @@ void MappedFileRegionBumpPtr::destroyImpl() {
189222

190223
// Attempt to truncate the file if we can get exclusive access. Ignore any
191224
// errors.
192-
if (BumpPtr) {
225+
if (H) {
193226
assert(SharedLockFD && "Must have shared lock file open");
194227
if (tryLockFileThreadSafe(*SharedLockFD) == std::error_code()) {
195228
size_t Size = size();
@@ -223,15 +256,15 @@ void MappedFileRegionBumpPtr::destroyImpl() {
223256

224257
void MappedFileRegionBumpPtr::initializeBumpPtr(int64_t BumpPtrOffset) {
225258
assert(capacity() < (uint64_t)INT64_MAX && "capacity must fit in int64_t");
226-
int64_t BumpPtrEndOffset = BumpPtrOffset + sizeof(decltype(*BumpPtr));
259+
int64_t BumpPtrEndOffset = BumpPtrOffset + sizeof(decltype(*H));
227260
assert(BumpPtrEndOffset <= (int64_t)capacity() &&
228261
"Expected end offset to be pre-allocated");
229-
assert(isAligned(Align::Of<decltype(*BumpPtr)>(), BumpPtrOffset) &&
262+
assert(isAligned(Align::Of<decltype(*H)>(), BumpPtrOffset) &&
230263
"Expected end offset to be aligned");
231-
BumpPtr = reinterpret_cast<decltype(BumpPtr)>(data() + BumpPtrOffset);
264+
H = reinterpret_cast<decltype(H)>(data() + BumpPtrOffset);
232265

233266
int64_t ExistingValue = 0;
234-
if (!BumpPtr->compare_exchange_strong(ExistingValue, BumpPtrEndOffset))
267+
if (!H->BumpPtr.compare_exchange_strong(ExistingValue, BumpPtrEndOffset))
235268
assert(ExistingValue >= BumpPtrEndOffset &&
236269
"Expected 0, or past the end of the BumpPtr itself");
237270

@@ -247,7 +280,7 @@ static Error createAllocatorOutOfSpaceError() {
247280

248281
Expected<int64_t> MappedFileRegionBumpPtr::allocateOffset(uint64_t AllocSize) {
249282
AllocSize = alignTo(AllocSize, getAlign());
250-
int64_t OldEnd = BumpPtr->fetch_add(AllocSize);
283+
int64_t OldEnd = H->BumpPtr.fetch_add(AllocSize);
251284
int64_t NewEnd = OldEnd + AllocSize;
252285
if (LLVM_UNLIKELY(NewEnd > (int64_t)capacity())) {
253286
// Return the allocation. If the start already passed the end, that means
@@ -257,7 +290,7 @@ Expected<int64_t> MappedFileRegionBumpPtr::allocateOffset(uint64_t AllocSize) {
257290
// All other allocation afterwards must have failed and current allocation
258291
// is in charge of return the allocation back to a valid value.
259292
if (OldEnd <= (int64_t)capacity())
260-
(void)BumpPtr->exchange(OldEnd);
293+
(void)H->BumpPtr.exchange(OldEnd);
261294

262295
if (Logger)
263296
Logger->log_MappedFileRegionBumpPtr_oom(Path, capacity(), OldEnd,
@@ -266,8 +299,43 @@ Expected<int64_t> MappedFileRegionBumpPtr::allocateOffset(uint64_t AllocSize) {
266299
return createAllocatorOutOfSpaceError();
267300
}
268301

302+
int64_t DiskSize = H->AllocatedSize;
303+
if (LLVM_UNLIKELY(NewEnd > DiskSize)) {
304+
int64_t NewSize;
305+
// The minimum increment is a page, but allocate more to amortize the cost.
306+
constexpr int64_t Increment = 1 * 1024 * 1024; // 1 MB
307+
if (Error E = preallocateFileTail(*FD, DiskSize, DiskSize + Increment).moveInto(NewSize))
308+
return std::move(E);
309+
assert(NewSize >= DiskSize + Increment);
310+
// FIXME: on Darwin this can under-count the size if there is a race to
311+
// preallocate disk, because the semantics of F_PREALLOCATE are to add bytes
312+
// to the end of the file, not to allocate up to a fixed size.
313+
// Any discrepancy will be resolved the next time the file is truncated and
314+
// then reopend.
315+
while (DiskSize < NewSize)
316+
H->AllocatedSize.compare_exchange_strong(DiskSize, NewSize);
317+
}
318+
269319
if (Logger)
270320
Logger->log_MappedFileRegionBumpPtr_allocate(data(), OldEnd, AllocSize);
271321

272322
return OldEnd;
273323
}
324+
325+
ErrorOr<FileSizeInfo> FileSizeInfo::get(sys::fs::file_t File) {
326+
#if LLVM_ON_UNIX && defined(MAPPED_FILE_BSIZE)
327+
struct stat Status;
328+
int StatRet = ::fstat(File, &Status);
329+
if (StatRet)
330+
return errnoAsErrorCode();
331+
uint64_t AllocatedSize = uint64_t(Status.st_blksize) * MAPPED_FILE_BSIZE;
332+
return FileSizeInfo{uint64_t(Status.st_size), AllocatedSize};
333+
#else
334+
// Fallback: assume the file is fully allocated. Note: this may result in
335+
// data loss on out-of-space.
336+
sys::fs::file_status Status;
337+
if (std::error_code EC = sys::fs::status(File, Status))
338+
return EC;
339+
return FileSizeInfo{Status.getSize(), Status.getSize()};
340+
#endif
341+
}

llvm/lib/CAS/OnDiskCommon.cpp

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "OnDiskCommon.h"
1010
#include "llvm/ADT/StringRef.h"
11+
#include "llvm/Config/config.h"
1112
#include "llvm/Support/Error.h"
1213
#include "llvm/Support/Process.h"
1314
#include <mutex>
@@ -23,6 +24,10 @@
2324
#endif
2425
#endif
2526

27+
#if __has_include(<fcntl.h>)
28+
#include <fcntl.h>
29+
#endif
30+
2631
using namespace llvm;
2732

2833
static uint64_t OnDiskCASMaxMappingSize = 0;
@@ -107,4 +112,32 @@ cas::ondisk::tryLockFileThreadSafe(int FD, std::chrono::milliseconds Timeout,
107112
#else
108113
return make_error_code(std::errc::no_lock_available);
109114
#endif
110-
}
115+
}
116+
117+
Expected<size_t> cas::ondisk::preallocateFileTail(int FD, size_t CurrentSize, size_t NewSize) {
118+
auto CreateErrorFromErrno = [&]() -> Expected<size_t> {
119+
std::error_code EC = errnoAsErrorCode();
120+
if (EC == std::errc::not_supported)
121+
// Ignore ENOTSUP in case the filesystem cannot preallocate.
122+
return NewSize;
123+
return createStringError(EC, "failed to allocate to CAS file: " + EC.message());
124+
};
125+
#if defined(HAVE_POSIX_FALLOCATE)
126+
if (posix_fallocate(FD, CurrentSize, NewSize - CurrentSize))
127+
return CreateErrorFromErrno();
128+
return NewSize;
129+
#elif defined(__APPLE__)
130+
fstore_t FAlloc;
131+
FAlloc.fst_flags = F_ALLOCATEALL | F_ALLOCATEPERSIST;
132+
FAlloc.fst_posmode = F_PEOFPOSMODE;
133+
FAlloc.fst_offset = 0;
134+
FAlloc.fst_length = NewSize - CurrentSize;
135+
FAlloc.fst_bytesalloc = 0;
136+
if (fcntl(FD, F_PREALLOCATE, &FAlloc))
137+
return CreateErrorFromErrno();
138+
assert(CurrentSize + FAlloc.fst_bytesalloc >= NewSize);
139+
return CurrentSize + FAlloc.fst_bytesalloc;
140+
#else
141+
return NewSize; // Pretend it worked.
142+
#endif
143+
}

llvm/lib/CAS/OnDiskCommon.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ std::error_code tryLockFileThreadSafe(
4343
int FD, std::chrono::milliseconds Timeout = std::chrono::milliseconds(0),
4444
bool Exclusive = true);
4545

46+
/// Allocate space for the file \p FD on disk, if the filesystem supports it.
47+
///
48+
/// On filesystems that support this operation, this ensures errors such as
49+
/// \c std::errc::no_space_on_device are detected before we write data.
50+
///
51+
/// \returns the new size of the file, or an \c Error.
52+
Expected<size_t> preallocateFileTail(int FD, size_t CurrentSize, size_t NewSize);
53+
4654
} // namespace llvm::cas::ondisk
4755

4856
#endif // LLVM_LIB_CAS_ONDISKCOMMON_H

llvm/lib/CAS/OnDiskGraphDB.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ static constexpr StringLiteral DataPoolTableName = "llvm.cas.data";
8181
static constexpr StringLiteral IndexFile = "index";
8282
static constexpr StringLiteral DataPoolFile = "data";
8383

84-
static constexpr StringLiteral FilePrefix = "v8.";
84+
static constexpr StringLiteral FilePrefix = "v9.";
8585
static constexpr StringLiteral FileSuffixData = ".data";
8686
static constexpr StringLiteral FileSuffixLeaf = ".leaf";
8787
static constexpr StringLiteral FileSuffixLeaf0 = ".leaf+0";
@@ -1311,6 +1311,9 @@ OnDiskGraphDB::createTempFile(StringRef FinalPath, uint64_t Size) {
13111311
if (!File)
13121312
return File.takeError();
13131313

1314+
if (Error E = preallocateFileTail(File->FD, 0, Size).takeError())
1315+
return createFileError(File->TmpName, std::move(E));
1316+
13141317
if (auto EC = sys::fs::resize_file_before_mapping_readwrite(File->FD, Size))
13151318
return createFileError(File->TmpName, EC);
13161319

llvm/lib/CAS/OnDiskKeyValueDB.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ using namespace llvm::cas;
1919
using namespace llvm::cas::ondisk;
2020

2121
static constexpr StringLiteral ActionCacheFile = "actions";
22-
static constexpr StringLiteral FilePrefix = "v3.";
22+
static constexpr StringLiteral FilePrefix = "v4.";
2323

2424
Expected<ArrayRef<char>> OnDiskKeyValueDB::put(ArrayRef<uint8_t> Key,
2525
ArrayRef<char> Value) {

0 commit comments

Comments
 (0)