Skip to content

Commit abb1349

Browse files
authored
Merge pull request #2858 from niw/fix_nsdata_ignores_umask
Create a file with permissions that respects umask
2 parents 970fcf9 + 2c8ff0a commit abb1349

File tree

3 files changed

+89
-5
lines changed

3 files changed

+89
-5
lines changed

Sources/Foundation/NSData.swift

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,7 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
434434
}
435435

436436
let fm = FileManager.default
437-
// The destination file path may not exist so provide a default file permissions of RW user only
438-
let permissions = (try? fm._permissionsOfItem(atPath: path)) ?? 0o600
437+
let permissions = try? fm._permissionsOfItem(atPath: path)
439438

440439
if writeOptionsMask.contains(.atomic) {
441440
let (newFD, auxFilePath) = try _NSCreateTemporaryFile(path)
@@ -446,7 +445,9 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
446445
// requires that there be no open handles to the file
447446
fh.closeFile()
448447
try _NSCleanupTemporaryFile(auxFilePath, path)
449-
try fm.setAttributes([.posixPermissions: NSNumber(value: permissions)], ofItemAtPath: path)
448+
if let permissions = permissions {
449+
try fm.setAttributes([.posixPermissions: NSNumber(value: permissions)], ofItemAtPath: path)
450+
}
450451
} catch {
451452
let savedErrno = errno
452453
try? fm.removeItem(atPath: auxFilePath)
@@ -458,10 +459,23 @@ open class NSData : NSObject, NSCopying, NSMutableCopying, NSSecureCoding {
458459
flags |= O_EXCL
459460
}
460461

461-
guard let fh = FileHandle(path: path, flags: flags, createMode: permissions) else {
462+
// NOTE: Each flag such as `S_IRUSR` may be literal depends on the system.
463+
// Without explicity type them as `Int`, type inference will not complete in reasonable time
464+
// and the compiler will throw an error.
465+
#if os(Windows)
466+
let createMode = Int(ucrt.S_IREAD) | Int(ucrt.S_IWRITE)
467+
#elseif canImport(Darwin)
468+
let createMode = Int(S_IRUSR) | Int(S_IWUSR) | Int(S_IRGRP) | Int(S_IWGRP) | Int(S_IROTH) | Int(S_IWOTH)
469+
#else
470+
let createMode = Int(Glibc.S_IRUSR) | Int(Glibc.S_IWUSR) | Int(Glibc.S_IRGRP) | Int(Glibc.S_IWGRP) | Int(Glibc.S_IROTH) | Int(Glibc.S_IWOTH)
471+
#endif
472+
guard let fh = FileHandle(path: path, flags: flags, createMode: createMode) else {
462473
throw _NSErrorWithErrno(errno, reading: false, path: path)
463474
}
464475
try doWrite(fh)
476+
if let permissions = permissions {
477+
try fm.setAttributes([.posixPermissions: NSNumber(value: permissions)], ofItemAtPath: path)
478+
}
465479
}
466480
}
467481

Sources/Foundation/NSPathUtilities.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -760,7 +760,10 @@ internal func _NSCreateTemporaryFile(_ filePath: String) throws -> (Int32, Strin
760760
let maxLength = Int(PATH_MAX) + 1
761761
var buf = [Int8](repeating: 0, count: maxLength)
762762
let _ = template._nsObject.getFileSystemRepresentation(&buf, maxLength: maxLength)
763-
let fd = mkstemp(&buf)
763+
guard let name = mktemp(&buf) else {
764+
throw _NSErrorWithErrno(errno, reading: false, path: filePath)
765+
}
766+
let fd = open(name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
764767
if fd == -1 {
765768
throw _NSErrorWithErrno(errno, reading: false, path: filePath)
766769
}

Tests/Foundation/Tests/TestNSData.swift

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@
77
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
88
//
99

10+
import CoreFoundation
11+
12+
#if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT
13+
#if canImport(SwiftFoundation) && !DEPLOYMENT_RUNTIME_OBJC
14+
@testable import SwiftFoundation
15+
#else
16+
@testable import Foundation
17+
#endif
18+
#endif
19+
1020
class TestNSData: LoopbackServerTest {
1121

1222
class AllOnesImmutableData : NSData {
@@ -223,6 +233,8 @@ class TestNSData: LoopbackServerTest {
223233
("test_limitDebugDescription", test_limitDebugDescription),
224234
("test_edgeDebugDescription", test_edgeDebugDescription),
225235
("test_writeToURLOptions", test_writeToURLOptions),
236+
("test_writeToURLPermissions", test_writeToURLPermissions),
237+
("test_writeToURLPermissionsWithAtomic", test_writeToURLPermissionsWithAtomic),
226238
("test_edgeNoCopyDescription", test_edgeNoCopyDescription),
227239
("test_initializeWithBase64EncodedDataGetsDecodedData", test_initializeWithBase64EncodedDataGetsDecodedData),
228240
("test_initializeWithBase64EncodedDataWithNonBase64CharacterIsNil", test_initializeWithBase64EncodedDataWithNonBase64CharacterIsNil),
@@ -551,6 +563,61 @@ class TestNSData: LoopbackServerTest {
551563
}
552564
}
553565

566+
#if !os(Windows)
567+
// NOTE: `umask(3)` is process global. Therefore, the behavior is unknown if `withUmask(_:_:)` is used simultaniously.
568+
private func withUmask(_ mode: mode_t, _ block: () -> Void) {
569+
let original = umask(mode)
570+
block()
571+
umask(original)
572+
}
573+
#endif
574+
575+
func test_writeToURLPermissions() {
576+
#if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT && !os(Windows)
577+
withUmask(0) {
578+
do {
579+
let data = Data()
580+
let url = URL(fileURLWithPath: NSTemporaryDirectory() + "meow")
581+
try data.write(to: url)
582+
let fileManager = FileManager.default
583+
let permission = try fileManager._permissionsOfItem(atPath: url.path)
584+
#if canImport(Darwin)
585+
let expected = Int(S_IRUSR) | Int(S_IWUSR) | Int(S_IRGRP) | Int(S_IWGRP) | Int(S_IROTH) | Int(S_IWOTH)
586+
#else
587+
let expected = Int(Glibc.S_IRUSR) | Int(Glibc.S_IWUSR) | Int(Glibc.S_IRGRP) | Int(Glibc.S_IWGRP) | Int(Glibc.S_IROTH) | Int(Glibc.S_IWOTH)
588+
#endif
589+
XCTAssertEqual(permission, expected)
590+
try! fileManager.removeItem(atPath: url.path)
591+
} catch {
592+
XCTFail()
593+
}
594+
}
595+
#endif
596+
}
597+
598+
func test_writeToURLPermissionsWithAtomic() {
599+
#if NS_FOUNDATION_ALLOWS_TESTABLE_IMPORT && !os(Windows)
600+
withUmask(0) {
601+
do {
602+
let data = Data()
603+
let url = URL(fileURLWithPath: NSTemporaryDirectory() + "meow")
604+
try data.write(to: url, options: .atomic)
605+
let fileManager = FileManager.default
606+
let permission = try fileManager._permissionsOfItem(atPath: url.path)
607+
#if canImport(Darwin)
608+
let expected = Int(S_IRUSR) | Int(S_IWUSR) | Int(S_IRGRP) | Int(S_IWGRP) | Int(S_IROTH) | Int(S_IWOTH)
609+
#else
610+
let expected = Int(Glibc.S_IRUSR) | Int(Glibc.S_IWUSR) | Int(Glibc.S_IRGRP) | Int(Glibc.S_IWGRP) | Int(Glibc.S_IROTH) | Int(Glibc.S_IWOTH)
611+
#endif
612+
XCTAssertEqual(permission, expected)
613+
try! fileManager.removeItem(atPath: url.path)
614+
} catch {
615+
XCTFail()
616+
}
617+
}
618+
#endif
619+
}
620+
554621
func test_emptyDescription() {
555622
let expected = "<>"
556623

0 commit comments

Comments
 (0)