Skip to content

Commit d393001

Browse files
authored
Merge pull request #1540 from spevans/pr_fsrep
2 parents 9e2e975 + 38b155b commit d393001

File tree

4 files changed

+179
-121
lines changed

4 files changed

+179
-121
lines changed

Foundation/FileManager.swift

Lines changed: 118 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -188,9 +188,11 @@ open class FileManager : NSObject {
188188
#elseif os(Linux) || os(Android) || CYGWIN
189189
let modeT = number.uint32Value
190190
#endif
191-
if chmod(path, mode_t(modeT)) != 0 {
192-
fatalError("errno \(errno)")
193-
}
191+
_fileSystemRepresentation(withPath: path, {
192+
if chmod($0, mode_t(modeT)) != 0 {
193+
fatalError("errno \(errno)")
194+
}
195+
})
194196
} else {
195197
fatalError("Attribute type not implemented: \(attribute)")
196198
}
@@ -202,30 +204,32 @@ open class FileManager : NSObject {
202204
This method replaces createDirectoryAtPath:attributes:
203205
*/
204206
open func createDirectory(atPath path: String, withIntermediateDirectories createIntermediates: Bool, attributes: [FileAttributeKey : Any]? = [:]) throws {
205-
if createIntermediates {
206-
var isDir: ObjCBool = false
207-
if !fileExists(atPath: path, isDirectory: &isDir) {
208-
let parent = path._nsObject.deletingLastPathComponent
209-
if !parent.isEmpty && !fileExists(atPath: parent, isDirectory: &isDir) {
210-
try createDirectory(atPath: parent, withIntermediateDirectories: true, attributes: attributes)
207+
try _fileSystemRepresentation(withPath: path, { pathFsRep in
208+
if createIntermediates {
209+
var isDir: ObjCBool = false
210+
if !fileExists(atPath: path, isDirectory: &isDir) {
211+
let parent = path._nsObject.deletingLastPathComponent
212+
if !parent.isEmpty && !fileExists(atPath: parent, isDirectory: &isDir) {
213+
try createDirectory(atPath: parent, withIntermediateDirectories: true, attributes: attributes)
214+
}
215+
if mkdir(pathFsRep, S_IRWXU | S_IRWXG | S_IRWXO) != 0 {
216+
throw _NSErrorWithErrno(errno, reading: false, path: path)
217+
} else if let attr = attributes {
218+
try self.setAttributes(attr, ofItemAtPath: path)
219+
}
220+
} else if isDir.boolValue {
221+
return
222+
} else {
223+
throw _NSErrorWithErrno(EEXIST, reading: false, path: path)
211224
}
212-
if mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO) != 0 {
225+
} else {
226+
if mkdir(pathFsRep, S_IRWXU | S_IRWXG | S_IRWXO) != 0 {
213227
throw _NSErrorWithErrno(errno, reading: false, path: path)
214228
} else if let attr = attributes {
215229
try self.setAttributes(attr, ofItemAtPath: path)
216230
}
217-
} else if isDir.boolValue {
218-
return
219-
} else {
220-
throw _NSErrorWithErrno(EEXIST, reading: false, path: path)
221-
}
222-
} else {
223-
if mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO) != 0 {
224-
throw _NSErrorWithErrno(errno, reading: false, path: path)
225-
} else if let attr = attributes {
226-
try self.setAttributes(attr, ofItemAtPath: path)
227231
}
228-
}
232+
})
229233
}
230234

231235
private func _contentsOfDir(atPath path: String, _ closure: (String, Int32) throws -> () ) throws {
@@ -310,10 +314,7 @@ open class FileManager : NSObject {
310314
This method replaces fileAttributesAtPath:traverseLink:.
311315
*/
312316
open func attributesOfItem(atPath path: String) throws -> [FileAttributeKey : Any] {
313-
var s = stat()
314-
guard lstat(path, &s) == 0 else {
315-
throw _NSErrorWithErrno(errno, reading: true, path: path)
316-
}
317+
let s = try _lstatFile(atPath: path)
317318
var result = [FileAttributeKey : Any]()
318319
result[.size] = NSNumber(value: UInt64(s.st_size))
319320

@@ -415,9 +416,11 @@ open class FileManager : NSObject {
415416
This method replaces createSymbolicLinkAtPath:pathContent:
416417
*/
417418
open func createSymbolicLink(atPath path: String, withDestinationPath destPath: String) throws {
418-
if symlink(destPath, path) == -1 {
419-
throw _NSErrorWithErrno(errno, reading: false, path: path)
420-
}
419+
try _fileSystemRepresentation(withPath: path, andPath: destPath, {
420+
guard symlink($1, $0) == 0 else {
421+
throw _NSErrorWithErrno(errno, reading: false, path: path)
422+
}
423+
})
421424
}
422425

423426
/* destinationOfSymbolicLinkAtPath:error: returns an NSString containing the path of the item pointed at by the symlink specified by 'path'. If this method returns 'nil', an NSError will be returned by reference in the 'error' parameter.
@@ -427,7 +430,9 @@ open class FileManager : NSObject {
427430
open func destinationOfSymbolicLink(atPath path: String) throws -> String {
428431
let bufSize = Int(PATH_MAX + 1)
429432
var buf = [Int8](repeating: 0, count: bufSize)
430-
let len = readlink(path, &buf, bufSize)
433+
let len = _fileSystemRepresentation(withPath: path) {
434+
readlink($0, &buf, bufSize)
435+
}
431436
if len < 0 {
432437
throw _NSErrorWithErrno(errno, reading: true, path: path)
433438
}
@@ -508,6 +513,22 @@ open class FileManager : NSObject {
508513
}
509514
}
510515

516+
private func _copySymlink(atPath srcPath: String, toPath dstPath: String) throws {
517+
let bufSize = Int(PATH_MAX) + 1
518+
var buf = [Int8](repeating: 0, count: bufSize)
519+
520+
try _fileSystemRepresentation(withPath: srcPath) { srcFsRep in
521+
let len = readlink(srcFsRep, &buf, bufSize)
522+
if len < 0 {
523+
throw _NSErrorWithErrno(errno, reading: true, path: srcPath)
524+
}
525+
try _fileSystemRepresentation(withPath: dstPath) { dstFsRep in
526+
if symlink(buf, dstFsRep) == -1 {
527+
throw _NSErrorWithErrno(errno, reading: false, path: dstPath)
528+
}
529+
}
530+
}
531+
}
511532

512533
open func copyItem(atPath srcPath: String, toPath dstPath: String) throws {
513534
guard
@@ -519,8 +540,7 @@ open class FileManager : NSObject {
519540

520541
func copyNonDirectory(srcPath: String, dstPath: String, fileType: FileAttributeType) throws {
521542
if fileType == .typeSymbolicLink {
522-
let destination = try destinationOfSymbolicLink(atPath: srcPath)
523-
try createSymbolicLink(atPath: dstPath, withDestinationPath: destination)
543+
try _copySymlink(atPath: srcPath, toPath: dstPath)
524544
} else if fileType == .typeRegular {
525545
try _copyRegularFile(atPath: srcPath, toPath: dstPath)
526546
}
@@ -553,24 +573,28 @@ open class FileManager : NSObject {
553573
guard !self.fileExists(atPath: dstPath) else {
554574
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileWriteFileExists.rawValue, userInfo: [NSFilePathErrorKey : NSString(dstPath)])
555575
}
556-
if rename(srcPath, dstPath) != 0 {
557-
if errno == EXDEV {
558-
// TODO: Copy and delete.
559-
NSUnimplemented("Cross-device moves not yet implemented")
560-
} else {
561-
throw _NSErrorWithErrno(errno, reading: false, path: srcPath)
576+
try _fileSystemRepresentation(withPath: srcPath, andPath: dstPath, {
577+
if rename($0, $1) != 0 {
578+
if errno == EXDEV {
579+
// TODO: Copy and delete.
580+
NSUnimplemented("Cross-device moves not yet implemented")
581+
} else {
582+
throw _NSErrorWithErrno(errno, reading: false, path: srcPath)
583+
}
562584
}
563-
}
585+
})
564586
}
565587

566588
open func linkItem(atPath srcPath: String, toPath dstPath: String) throws {
567589
var isDir: ObjCBool = false
568590
if self.fileExists(atPath: srcPath, isDirectory: &isDir) {
569591
if !isDir.boolValue {
570592
// TODO: Symlinks should be copied instead of hard-linked.
571-
if link(srcPath, dstPath) == -1 {
572-
throw _NSErrorWithErrno(errno, reading: false, path: srcPath)
573-
}
593+
try _fileSystemRepresentation(withPath: srcPath, andPath: dstPath, {
594+
if link($0, $1) == -1 {
595+
throw _NSErrorWithErrno(errno, reading: false, path: srcPath)
596+
}
597+
})
574598
} else {
575599
// TODO: Recurse through directories, copying them.
576600
NSUnimplemented("Recursive linking not yet implemented")
@@ -622,7 +646,7 @@ open class FileManager : NSObject {
622646

623647
} else if errno != ENOTDIR {
624648
throw _NSErrorWithErrno(errno, reading: false, path: path)
625-
} else if unlink(path) != 0 {
649+
} else if _fileSystemRepresentation(withPath: path, { unlink($0) != 0 }) {
626650
throw _NSErrorWithErrno(errno, reading: false, path: path)
627651
}
628652
}
@@ -676,7 +700,7 @@ open class FileManager : NSObject {
676700

677701
@discardableResult
678702
open func changeCurrentDirectoryPath(_ path: String) -> Bool {
679-
return chdir(path) == 0
703+
return _fileSystemRepresentation(withPath: path, { chdir($0) == 0 })
680704
}
681705

682706
/* The following methods are of limited utility. Attempting to predicate behavior based on the current state of the filesystem or a particular file on the filesystem is encouraging odd behavior in the face of filesystem race conditions. It's far better to attempt an operation (like loading a file or creating a directory) and handle the error gracefully than it is to try to figure out ahead of time whether the operation will succeed.
@@ -686,40 +710,47 @@ open class FileManager : NSObject {
686710
}
687711

688712
open func fileExists(atPath path: String, isDirectory: UnsafeMutablePointer<ObjCBool>?) -> Bool {
689-
var s = stat()
690-
guard lstat(path, &s) >= 0 else {
691-
return false
692-
}
693-
694-
if (s.st_mode & S_IFMT) == S_IFLNK {
695-
// don't chase the link for this magic case -- we might be /Net/foo
696-
// which is a symlink to /private/Net/foo which is not yet mounted...
697-
if isDirectory == nil && (s.st_mode & S_ISVTX) == S_ISVTX {
698-
return true
699-
}
700-
// chase the link; too bad if it is a slink to /Net/foo
701-
guard stat(path, &s) >= 0 else {
713+
return _fileSystemRepresentation(withPath: path, { fsRep in
714+
guard var s = try? _lstatFile(atPath: path, withFileSystemRepresentation: fsRep) else {
702715
return false
703716
}
704-
}
705717

706-
if let isDirectory = isDirectory {
707-
isDirectory.pointee = ObjCBool((s.st_mode & S_IFMT) == S_IFDIR)
708-
}
718+
if (s.st_mode & S_IFMT) == S_IFLNK {
719+
// don't chase the link for this magic case -- we might be /Net/foo
720+
// which is a symlink to /private/Net/foo which is not yet mounted...
721+
if isDirectory == nil && (s.st_mode & S_ISVTX) == S_ISVTX {
722+
return true
723+
}
724+
// chase the link; too bad if it is a slink to /Net/foo
725+
guard stat(fsRep, &s) >= 0 else {
726+
return false
727+
}
728+
}
709729

710-
return true
730+
if let isDirectory = isDirectory {
731+
isDirectory.pointee = ObjCBool((s.st_mode & S_IFMT) == S_IFDIR)
732+
}
733+
734+
return true
735+
})
711736
}
712737

713738
open func isReadableFile(atPath path: String) -> Bool {
714-
return access(path, R_OK) == 0
739+
return _fileSystemRepresentation(withPath: path, {
740+
access($0, R_OK) == 0
741+
})
715742
}
716743

717744
open func isWritableFile(atPath path: String) -> Bool {
718-
return access(path, W_OK) == 0
745+
return _fileSystemRepresentation(withPath: path, {
746+
access($0, W_OK) == 0
747+
})
719748
}
720749

721750
open func isExecutableFile(atPath path: String) -> Bool {
722-
return access(path, X_OK) == 0
751+
return _fileSystemRepresentation(withPath: path, {
752+
access($0, X_OK) == 0
753+
})
723754
}
724755

725756
open func isDeletableFile(atPath path: String) -> Bool {
@@ -812,10 +843,14 @@ open class FileManager : NSObject {
812843
let _fsRep: UnsafePointer<Int8>
813844
if fsRep == nil {
814845
_fsRep = fileSystemRepresentation(withPath: path)
815-
defer { _fsRep.deallocate() }
816846
} else {
817847
_fsRep = fsRep!
818848
}
849+
850+
defer {
851+
if fsRep == nil { _fsRep.deallocate() }
852+
}
853+
819854
var statInfo = stat()
820855
guard lstat(_fsRep, &statInfo) == 0 else {
821856
throw _NSErrorWithErrno(errno, reading: true, path: path)
@@ -961,7 +996,24 @@ open class FileManager : NSObject {
961996
}
962997
return UnsafePointer(buf)
963998
}
964-
999+
1000+
internal func _fileSystemRepresentation<ResultType>(withPath path: String, _ body: (UnsafePointer<Int8>) throws -> ResultType) rethrows -> ResultType {
1001+
let fsRep = fileSystemRepresentation(withPath: path)
1002+
defer { fsRep.deallocate() }
1003+
return try body(fsRep)
1004+
}
1005+
1006+
internal func _fileSystemRepresentation<ResultType>(withPath path1: String, andPath path2: String, _ body: (UnsafePointer<Int8>, UnsafePointer<Int8>) throws -> ResultType) rethrows -> ResultType {
1007+
1008+
let fsRep1 = fileSystemRepresentation(withPath: path1)
1009+
let fsRep2 = fileSystemRepresentation(withPath: path2)
1010+
defer {
1011+
fsRep1.deallocate()
1012+
fsRep2.deallocate()
1013+
}
1014+
return try body(fsRep1, fsRep2)
1015+
}
1016+
9651017
/* stringWithFileSystemRepresentation:length: returns an NSString created from an array of bytes that are in the filesystem representation.
9661018
*/
9671019
open func string(withFileSystemRepresentation str: UnsafePointer<Int8>, length len: Int) -> String {

0 commit comments

Comments
 (0)