Skip to content

Commit 366a366

Browse files
spevansparkera
authored andcommitted
FileManager: Fix fileExists(atPath:isDirectory:) with dangling symlink (#1514)
- When the path item is a symlink and the target of the symlink does not exist, return false instead of true. - Combine the calls to stat() when the file is a symlink.
1 parent 90acdc7 commit 366a366

File tree

2 files changed

+63
-22
lines changed

2 files changed

+63
-22
lines changed

Foundation/FileManager.swift

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -593,32 +593,26 @@ open class FileManager : NSObject {
593593

594594
open func fileExists(atPath path: String, isDirectory: UnsafeMutablePointer<ObjCBool>?) -> Bool {
595595
var s = stat()
596-
if lstat(path, &s) >= 0 {
597-
if let isDirectory = isDirectory {
598-
if (s.st_mode & S_IFMT) == S_IFLNK {
599-
if stat(path, &s) >= 0 {
600-
isDirectory.pointee = ObjCBool((s.st_mode & S_IFMT) == S_IFDIR)
601-
} else {
602-
return false
603-
}
604-
} else {
605-
let isDir = (s.st_mode & S_IFMT) == S_IFDIR
606-
isDirectory.pointee = ObjCBool(isDir)
607-
}
608-
}
596+
guard lstat(path, &s) >= 0 else {
597+
return false
598+
}
609599

600+
if (s.st_mode & S_IFMT) == S_IFLNK {
610601
// don't chase the link for this magic case -- we might be /Net/foo
611602
// which is a symlink to /private/Net/foo which is not yet mounted...
612-
if (s.st_mode & S_IFMT) == S_IFLNK {
613-
if (s.st_mode & S_ISVTX) == S_ISVTX {
614-
return true
615-
}
616-
// chase the link; too bad if it is a slink to /Net/foo
617-
stat(path, &s)
603+
if isDirectory == nil && (s.st_mode & S_ISVTX) == S_ISVTX {
604+
return true
605+
}
606+
// chase the link; too bad if it is a slink to /Net/foo
607+
guard stat(path, &s) >= 0 else {
608+
return false
618609
}
619-
} else {
620-
return false
621610
}
611+
612+
if let isDirectory = isDirectory {
613+
isDirectory.pointee = ObjCBool((s.st_mode & S_IFMT) == S_IFDIR)
614+
}
615+
622616
return true
623617
}
624618

TestFoundation/TestFileManager.swift

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class TestFileManager : XCTestCase {
2323
("test_createFile", test_createFile ),
2424
("test_moveFile", test_moveFile),
2525
("test_fileSystemRepresentation", test_fileSystemRepresentation),
26+
("test_fileExists", test_fileExists),
2627
("test_fileAttributes", test_fileAttributes),
2728
("test_fileSystemAttributes", test_fileSystemAttributes),
2829
("test_setFileAttributes", test_setFileAttributes),
@@ -161,7 +162,53 @@ class TestFileManager : XCTestCase {
161162
result.deallocate()
162163
#endif
163164
}
164-
165+
166+
func test_fileExists() {
167+
let fm = FileManager.default
168+
let tmpDir = fm.temporaryDirectory.appendingPathComponent("testFileExistsDir")
169+
let testFile = tmpDir.appendingPathComponent("testFile")
170+
let goodSymLink = tmpDir.appendingPathComponent("goodSymLink")
171+
let badSymLink = tmpDir.appendingPathComponent("badSymLink")
172+
let dirSymLink = tmpDir.appendingPathComponent("dirSymlink")
173+
174+
ignoreError { try fm.removeItem(atPath: tmpDir.path) }
175+
176+
do {
177+
try fm.createDirectory(atPath: tmpDir.path, withIntermediateDirectories: false, attributes: nil)
178+
XCTAssertTrue(fm.createFile(atPath: testFile.path, contents: Data()))
179+
try fm.createSymbolicLink(atPath: goodSymLink.path, withDestinationPath: testFile.path)
180+
try fm.createSymbolicLink(atPath: badSymLink.path, withDestinationPath: "no_such_file")
181+
try fm.createSymbolicLink(atPath: dirSymLink.path, withDestinationPath: "..")
182+
183+
var isDirFlag: ObjCBool = false
184+
XCTAssertTrue(fm.fileExists(atPath: tmpDir.path))
185+
XCTAssertTrue(fm.fileExists(atPath: tmpDir.path, isDirectory: &isDirFlag))
186+
XCTAssertTrue(isDirFlag.boolValue)
187+
188+
isDirFlag = true
189+
XCTAssertTrue(fm.fileExists(atPath: testFile.path))
190+
XCTAssertTrue(fm.fileExists(atPath: testFile.path, isDirectory: &isDirFlag))
191+
XCTAssertFalse(isDirFlag.boolValue)
192+
193+
isDirFlag = true
194+
XCTAssertTrue(fm.fileExists(atPath: goodSymLink.path))
195+
XCTAssertTrue(fm.fileExists(atPath: goodSymLink.path, isDirectory: &isDirFlag))
196+
XCTAssertFalse(isDirFlag.boolValue)
197+
198+
isDirFlag = true
199+
XCTAssertFalse(fm.fileExists(atPath: badSymLink.path))
200+
XCTAssertFalse(fm.fileExists(atPath: badSymLink.path, isDirectory: &isDirFlag))
201+
202+
isDirFlag = false
203+
XCTAssertTrue(fm.fileExists(atPath: dirSymLink.path))
204+
XCTAssertTrue(fm.fileExists(atPath: dirSymLink.path, isDirectory: &isDirFlag))
205+
XCTAssertTrue(isDirFlag.boolValue)
206+
} catch {
207+
XCTFail(String(describing: error))
208+
}
209+
ignoreError { try fm.removeItem(atPath: tmpDir.path) }
210+
}
211+
165212
func test_fileAttributes() {
166213
let fm = FileManager.default
167214
let path = NSTemporaryDirectory() + "test_fileAttributes\(NSUUID().uuidString)"

0 commit comments

Comments
 (0)