Skip to content

Commit 107e570

Browse files
authored
[5.6] FileLock encounters a runtime error when the path of the locked file is long
2 parents c40a559 + bea2cf8 commit 107e570

File tree

2 files changed

+32
-5
lines changed

2 files changed

+32
-5
lines changed

Sources/TSCBasic/Lock.swift

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
4+
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See http://swift.org/LICENSE.txt for license information
@@ -178,12 +178,33 @@ public final class FileLock {
178178
throw FileSystemError(.notDirectory, lockFilesDirectory)
179179
}
180180
// use the parent path to generate unique filename in temp
181-
var lockFileName = (resolveSymlinks(fileToLock.parentDirectory).appending(component: fileToLock.basename)).components.joined(separator: "_")
181+
var lockFileName = (resolveSymlinks(fileToLock.parentDirectory)
182+
.appending(component: fileToLock.basename))
183+
.components.joined(separator: "_")
184+
.replacingOccurrences(of: ":", with: "_") + ".lock"
185+
#if os(Windows)
186+
// NTFS has an ARC limit of 255 codepoints
187+
var lockFileUTF16 = lockFileName.utf16.suffix(255)
188+
while String(lockFileUTF16) == nil {
189+
lockFileUTF16 = lockFileUTF16.dropFirst()
190+
}
191+
lockFileName = String(lockFileUTF16) ?? lockFileName
192+
#else
182193
if lockFileName.hasPrefix(AbsolutePath.root.pathString) {
183194
lockFileName = String(lockFileName.dropFirst(AbsolutePath.root.pathString.count))
184195
}
185-
let lockFilePath = lockFilesDirectory.appending(component: lockFileName + ".lock")
186-
196+
// back off until it occupies at most `NAME_MAX` UTF-8 bytes but without splitting scalars
197+
// (we might split clusters but it's not worth the effort to keep them together as long as we get a valid file name)
198+
var lockFileUTF8 = lockFileName.utf8.suffix(Int(NAME_MAX))
199+
while String(lockFileUTF8) == nil {
200+
// in practice this will only be a few iterations
201+
lockFileUTF8 = lockFileUTF8.dropFirst()
202+
}
203+
// we will never end up with nil since we have ASCII characters at the end
204+
lockFileName = String(lockFileUTF8) ?? lockFileName
205+
#endif
206+
let lockFilePath = lockFilesDirectory.appending(component: lockFileName)
207+
187208
let lock = FileLock(at: lockFilePath)
188209
return try lock.withLock(type: type, body)
189210
}

Tests/TSCBasicTests/FileSystemTests.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This source file is part of the Swift.org open source project
33

4-
Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
4+
Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
55
Licensed under Apache License v2.0 with Runtime Library Exception
66

77
See http://swift.org/LICENSE.txt for license information
@@ -767,6 +767,12 @@ class FileSystemTests: XCTestCase {
767767
let lockFile = tempDir.appending(component: "lockfile")
768768

769769
try _testFileSystemFileLock(fileSystem: localFileSystem, fileA: fileA, fileB: fileB, lockFile: lockFile)
770+
771+
// Test some long and edge case paths. We arrange to split between the C and the Cedilla by repeating 255 times.
772+
let longEdgeCase1 = tempDir.appending(component: String(repeating: "Façade! ", count: 255).decomposedStringWithCanonicalMapping)
773+
try localFileSystem.withLock(on: longEdgeCase1, type: .exclusive, {})
774+
let longEdgeCase2 = tempDir.appending(component: String(repeating: "🏁", count: 255).decomposedStringWithCanonicalMapping)
775+
try localFileSystem.withLock(on: longEdgeCase2, type: .exclusive, {})
770776
}
771777
}
772778

0 commit comments

Comments
 (0)