Skip to content

Commit aa5f75c

Browse files
committed
SR-6531: FileManager copyItem() does not preserve permissions
- Use fchmod() to set the file permissions after opening. - Ensure all file permissions are correctly masked using ~S_IFMT.
1 parent 9aabdda commit aa5f75c

File tree

2 files changed

+53
-3
lines changed

2 files changed

+53
-3
lines changed

Foundation/FileManager.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// This source file is part of the Swift.org open source project
22
//
3-
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
3+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
44
// Licensed under Apache License v2.0 with Runtime Library Exception
55
//
66
// See https://swift.org/LICENSE.txt for license information
@@ -673,7 +673,7 @@ open class FileManager : NSObject {
673673
#endif
674674
result[.modificationDate] = Date(timeIntervalSinceReferenceDate: ti)
675675

676-
result[.posixPermissions] = NSNumber(value: UInt64(s.st_mode & 0o7777))
676+
result[.posixPermissions] = NSNumber(value: UInt64(s.st_mode & ~S_IFMT))
677677
result[.referenceCount] = NSNumber(value: UInt64(s.st_nlink))
678678
result[.systemNumber] = NSNumber(value: UInt64(s.st_dev))
679679
result[.systemFileNumber] = NSNumber(value: UInt64(s.st_ino))
@@ -845,6 +845,13 @@ open class FileManager : NSObject {
845845
}
846846
defer { close(dstfd) }
847847

848+
// Set the file permissions using fchmod() instead of when open()ing to avoid umask() issues
849+
let permissions = fileInfo.st_mode & ~S_IFMT
850+
guard fchmod(dstfd, permissions) == 0 else {
851+
throw _NSErrorWithErrno(errno, reading: false, path: dstPath,
852+
extraUserInfo: extraErrorInfo(srcPath: srcPath, dstPath: dstPath, userVariant: variant))
853+
}
854+
848855
if fileInfo.st_size == 0 {
849856
// no copying required
850857
return
@@ -1394,7 +1401,7 @@ open class FileManager : NSObject {
13941401

13951402
internal func _permissionsOfItem(atPath path: String) throws -> Int {
13961403
let fileInfo = try _lstatFile(atPath: path)
1397-
return Int(fileInfo.st_mode & 0o777)
1404+
return Int(fileInfo.st_mode & ~S_IFMT)
13981405
}
13991406

14001407
/* -contentsEqualAtPath:andPath: does not take into account data stored in the resource fork or filesystem extended attributes.

TestFoundation/TestFileManager.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class TestFileManager : XCTestCase {
4141
("test_temporaryDirectoryForUser", test_temporaryDirectoryForUser),
4242
("test_creatingDirectoryWithShortIntermediatePath", test_creatingDirectoryWithShortIntermediatePath),
4343
("test_mountedVolumeURLs", test_mountedVolumeURLs),
44+
("test_copyItemsPermissions", test_copyItemsPermissions),
4445
]
4546

4647
#if !DEPLOYMENT_RUNTIME_OBJC && NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
@@ -1067,6 +1068,48 @@ class TestFileManager : XCTestCase {
10671068
XCTAssertFalse(fm.contentsEqual(atPath: dataFile1.path, andPath: dataFile2.path))
10681069
XCTAssertFalse(fm.contentsEqual(atPath: testDir1.path, andPath: testDir2.path))
10691070
}
1071+
1072+
func test_copyItemsPermissions() throws {
1073+
let fm = FileManager.default
1074+
let tmpDir = fm.temporaryDirectory.appendingPathComponent("test_copyItemsPermissions")
1075+
try fm.createDirectory(at: tmpDir, withIntermediateDirectories: true)
1076+
defer { try? fm.removeItem(atPath: tmpDir.path) }
1077+
1078+
let srcFile = tmpDir.appendingPathComponent("file1.txt")
1079+
let destFile = tmpDir.appendingPathComponent("file2.txt")
1080+
1081+
try? fm.removeItem(at: srcFile)
1082+
try "This is the source file".write(toFile: srcFile.path, atomically: false, encoding: .utf8)
1083+
1084+
func testCopy() throws {
1085+
try? fm.removeItem(at: destFile)
1086+
try fm.copyItem(at: srcFile, to: destFile)
1087+
1088+
if let srcPerms = (try fm.attributesOfItem(atPath: srcFile.path)[.posixPermissions] as? NSNumber)?.intValue,
1089+
let destPerms = (try fm.attributesOfItem(atPath: destFile.path)[.posixPermissions] as? NSNumber)?.intValue {
1090+
XCTAssertEqual(srcPerms, destPerms)
1091+
} else {
1092+
XCTFail("Cant get file permissions")
1093+
}
1094+
}
1095+
1096+
try testCopy()
1097+
1098+
try fm.setAttributes([ .posixPermissions: 0o417], ofItemAtPath: srcFile.path)
1099+
try testCopy()
1100+
1101+
try fm.setAttributes([ .posixPermissions: 0o400], ofItemAtPath: srcFile.path)
1102+
try testCopy()
1103+
1104+
try fm.setAttributes([ .posixPermissions: 0o700], ofItemAtPath: srcFile.path)
1105+
try testCopy()
1106+
1107+
try fm.setAttributes([ .posixPermissions: 0o707], ofItemAtPath: srcFile.path)
1108+
try testCopy()
1109+
1110+
try fm.setAttributes([ .posixPermissions: 0o411], ofItemAtPath: srcFile.path)
1111+
try testCopy()
1112+
}
10701113

10711114
#if !DEPLOYMENT_RUNTIME_OBJC // XDG tests require swift-corelibs-foundation
10721115

0 commit comments

Comments
 (0)