From 4e48bf502a6d0471938aba1012553901d9d47648 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 20 Apr 2023 14:35:46 +0100 Subject: [PATCH 1/7] Add `hasQuarantineAttribute` to `FileSystem` This new function returns `true` if a given path has a quarantine attribute applied if when file system supports this attribute, and returns `false` if such attribute is not applied or it isn't supported. --- Sources/TSCBasic/FileSystem.swift | 16 ++++++++++++++++ Tests/TSCBasicTests/FileSystemTests.swift | 13 +++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Sources/TSCBasic/FileSystem.swift b/Sources/TSCBasic/FileSystem.swift index 741aa93e..32b94d8e 100644 --- a/Sources/TSCBasic/FileSystem.swift +++ b/Sources/TSCBasic/FileSystem.swift @@ -168,6 +168,10 @@ public protocol FileSystem: AnyObject { /// Check whether the given path is accessible and writable. func isWritable(_ path: AbsolutePath) -> Bool + /// Returns `true` if a given path has a quarantine attribute applied if when file system supports this attribute. + /// Returns `false` if such attribute is not applied or it isn't supported. + func hasQuarantineAttribute(_ path: AbsolutePath) -> Bool + // FIXME: Actual file system interfaces will allow more efficient access to // more data than just the name here. // @@ -293,6 +297,8 @@ public extension FileSystem { func withLock(on path: AbsolutePath, type: FileLock.LockType, _ body: () throws -> T) throws -> T { throw FileSystemError(.unsupported, path) } + + func hasQuarantineAttribute(_ path: AbsolutePath) -> Bool { false } } /// Concrete FileSystem implementation which communicates with the local file system. @@ -342,6 +348,16 @@ private class LocalFileSystem: FileSystem { return FileInfo(attrs) } + func hasQuarantineAttribute(_ path: AbsolutePath) -> Bool { +#if canImport(Darwin) + let bufLength = getxattr(path.pathString, "com.apple.quarantine", nil, 0, 0, 0) + + return bufLength > 0 +#else + return false +#endif + } + var currentWorkingDirectory: AbsolutePath? { let cwdStr = FileManager.default.currentDirectoryPath diff --git a/Tests/TSCBasicTests/FileSystemTests.swift b/Tests/TSCBasicTests/FileSystemTests.swift index 8fc62006..f1e14b6e 100644 --- a/Tests/TSCBasicTests/FileSystemTests.swift +++ b/Tests/TSCBasicTests/FileSystemTests.swift @@ -860,6 +860,19 @@ class FileSystemTests: XCTestCase { try _testFileSystemFileLock(fileSystem: fs, fileA: fileA, fileB: fileB, lockFile: lockFile) } +#if canImport(Darwin) + func testQuarantineAttribute() throws { + try withTemporaryDirectory(removeTreeOnDeinit: true) { tempDir in + let filePath = tempDir.appending(component: "quarantined") + try localFileSystem.writeFileContents(filePath, bytes: "") + try Process.checkNonZeroExit(args: "xattr", "-w", "com.apple.quarantine", "foo", filePath.pathString) + XCTAssertTrue(localFileSystem.hasQuarantineAttribute(filePath)) + try Process.checkNonZeroExit(args: "xattr", "-d", "com.apple.quarantine", filePath.pathString) + XCTAssertFalse(localFileSystem.hasQuarantineAttribute(filePath)) + } + } +#endif + private func _testFileSystemFileLock(fileSystem fs: FileSystem, fileA: AbsolutePath, fileB: AbsolutePath, lockFile: AbsolutePath) throws { // write initial value, since reader may start before writers and files would not exist try fs.writeFileContents(fileA, bytes: "0") From 34ca81a359524794be2a275f78c4e2ef97a98b84 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Fri, 21 Apr 2023 15:44:57 +0100 Subject: [PATCH 2/7] Change `hasQuarantineAttribute(_:)` to `hasAttribute(name:_:)` --- Sources/TSCBasic/FileSystem.swift | 14 +++++++------- Tests/TSCBasicTests/FileSystemTests.swift | 11 ++++++----- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Sources/TSCBasic/FileSystem.swift b/Sources/TSCBasic/FileSystem.swift index 32b94d8e..f2a574f9 100644 --- a/Sources/TSCBasic/FileSystem.swift +++ b/Sources/TSCBasic/FileSystem.swift @@ -168,9 +168,9 @@ public protocol FileSystem: AnyObject { /// Check whether the given path is accessible and writable. func isWritable(_ path: AbsolutePath) -> Bool - /// Returns `true` if a given path has a quarantine attribute applied if when file system supports this attribute. - /// Returns `false` if such attribute is not applied or it isn't supported. - func hasQuarantineAttribute(_ path: AbsolutePath) -> Bool + /// Returns `true` if a given path has an attribute with a given name applied when file system supports this + /// attribute. Returns `false` if such attribute is not applied or it isn't supported. + func hasAttribute(name: String, _ path: AbsolutePath) -> Bool // FIXME: Actual file system interfaces will allow more efficient access to // more data than just the name here. @@ -298,7 +298,7 @@ public extension FileSystem { throw FileSystemError(.unsupported, path) } - func hasQuarantineAttribute(_ path: AbsolutePath) -> Bool { false } + func hasAttribute(name: String, _ path: AbsolutePath) -> Bool { false } } /// Concrete FileSystem implementation which communicates with the local file system. @@ -348,9 +348,9 @@ private class LocalFileSystem: FileSystem { return FileInfo(attrs) } - func hasQuarantineAttribute(_ path: AbsolutePath) -> Bool { -#if canImport(Darwin) - let bufLength = getxattr(path.pathString, "com.apple.quarantine", nil, 0, 0, 0) + func hasAttribute(name: String, _ path: AbsolutePath) -> Bool { +#if canImport(Darwin) || canImport(Glibc) + let bufLength = getxattr(path.pathString, name, nil, 0, 0, 0) return bufLength > 0 #else diff --git a/Tests/TSCBasicTests/FileSystemTests.swift b/Tests/TSCBasicTests/FileSystemTests.swift index f1e14b6e..5b718a4e 100644 --- a/Tests/TSCBasicTests/FileSystemTests.swift +++ b/Tests/TSCBasicTests/FileSystemTests.swift @@ -860,15 +860,16 @@ class FileSystemTests: XCTestCase { try _testFileSystemFileLock(fileSystem: fs, fileA: fileA, fileB: fileB, lockFile: lockFile) } -#if canImport(Darwin) +#if canImport(Darwin) || canImport(Glibc) func testQuarantineAttribute() throws { try withTemporaryDirectory(removeTreeOnDeinit: true) { tempDir in let filePath = tempDir.appending(component: "quarantined") + let attributeName = "com.apple.quarantine" try localFileSystem.writeFileContents(filePath, bytes: "") - try Process.checkNonZeroExit(args: "xattr", "-w", "com.apple.quarantine", "foo", filePath.pathString) - XCTAssertTrue(localFileSystem.hasQuarantineAttribute(filePath)) - try Process.checkNonZeroExit(args: "xattr", "-d", "com.apple.quarantine", filePath.pathString) - XCTAssertFalse(localFileSystem.hasQuarantineAttribute(filePath)) + try Process.checkNonZeroExit(args: "xattr", "-w", attributeName, "foo", filePath.pathString) + XCTAssertTrue(localFileSystem.hasAttribute(name: attributeName, filePath)) + try Process.checkNonZeroExit(args: "xattr", "-d", attributeName, filePath.pathString) + XCTAssertFalse(localFileSystem.hasAttribute(name: attributeName, filePath)) } } #endif From 964066362f44cdf38e816590dada82fba0bb40b5 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Fri, 21 Apr 2023 15:58:19 +0100 Subject: [PATCH 3/7] Remove `canImport(Glibc)` check --- Sources/TSCBasic/FileSystem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/TSCBasic/FileSystem.swift b/Sources/TSCBasic/FileSystem.swift index f2a574f9..3a581291 100644 --- a/Sources/TSCBasic/FileSystem.swift +++ b/Sources/TSCBasic/FileSystem.swift @@ -349,7 +349,7 @@ private class LocalFileSystem: FileSystem { } func hasAttribute(name: String, _ path: AbsolutePath) -> Bool { -#if canImport(Darwin) || canImport(Glibc) +#if canImport(Darwin) let bufLength = getxattr(path.pathString, name, nil, 0, 0, 0) return bufLength > 0 From 777c8c491471aa0893fee3bd474ee41004a31f0d Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Fri, 21 Apr 2023 17:13:01 +0100 Subject: [PATCH 4/7] Fix Linux test --- Tests/TSCBasicTests/FileSystemTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/TSCBasicTests/FileSystemTests.swift b/Tests/TSCBasicTests/FileSystemTests.swift index 5b718a4e..7fecb9ed 100644 --- a/Tests/TSCBasicTests/FileSystemTests.swift +++ b/Tests/TSCBasicTests/FileSystemTests.swift @@ -860,8 +860,8 @@ class FileSystemTests: XCTestCase { try _testFileSystemFileLock(fileSystem: fs, fileA: fileA, fileB: fileB, lockFile: lockFile) } -#if canImport(Darwin) || canImport(Glibc) - func testQuarantineAttribute() throws { +#if canImport(Darwin) + func testHasAttribute() throws { try withTemporaryDirectory(removeTreeOnDeinit: true) { tempDir in let filePath = tempDir.appending(component: "quarantined") let attributeName = "com.apple.quarantine" From 52b2c6a3015ee2350907bf9b5b62332dd5ea7902 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 10 May 2023 15:10:21 +0100 Subject: [PATCH 5/7] Add `public enum FileSystemAttribute` --- Sources/TSCBasic/FileSystem.swift | 15 +++++++++++---- Tests/TSCBasicTests/FileSystemTests.swift | 9 ++++----- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Sources/TSCBasic/FileSystem.swift b/Sources/TSCBasic/FileSystem.swift index 3a581291..d97529bf 100644 --- a/Sources/TSCBasic/FileSystem.swift +++ b/Sources/TSCBasic/FileSystem.swift @@ -137,6 +137,13 @@ public enum FileMode: Sendable { } } +/// Extended file system attributes that can applied to a given file path. See also ``FileSystem/hasAttribute(_:_:)``. +public enum FileSystemAttribute: String { + #if canImport(Darwin) + case quarantine = "com.apple.quarantine" + #endif +} + // FIXME: Design an asynchronous story? // /// Abstracted access to file system operations. @@ -170,7 +177,7 @@ public protocol FileSystem: AnyObject { /// Returns `true` if a given path has an attribute with a given name applied when file system supports this /// attribute. Returns `false` if such attribute is not applied or it isn't supported. - func hasAttribute(name: String, _ path: AbsolutePath) -> Bool + func hasAttribute(_ name: FileSystemAttribute, _ path: AbsolutePath) -> Bool // FIXME: Actual file system interfaces will allow more efficient access to // more data than just the name here. @@ -298,7 +305,7 @@ public extension FileSystem { throw FileSystemError(.unsupported, path) } - func hasAttribute(name: String, _ path: AbsolutePath) -> Bool { false } + func hasAttribute(_ name: FileSystemAttribute, _ path: AbsolutePath) -> Bool { false } } /// Concrete FileSystem implementation which communicates with the local file system. @@ -348,9 +355,9 @@ private class LocalFileSystem: FileSystem { return FileInfo(attrs) } - func hasAttribute(name: String, _ path: AbsolutePath) -> Bool { + func hasAttribute(_ name: FileSystemAttribute, _ path: AbsolutePath) -> Bool { #if canImport(Darwin) - let bufLength = getxattr(path.pathString, name, nil, 0, 0, 0) + let bufLength = getxattr(path.pathString, name.rawValue, nil, 0, 0, 0) return bufLength > 0 #else diff --git a/Tests/TSCBasicTests/FileSystemTests.swift b/Tests/TSCBasicTests/FileSystemTests.swift index 7fecb9ed..8c3e8ad2 100644 --- a/Tests/TSCBasicTests/FileSystemTests.swift +++ b/Tests/TSCBasicTests/FileSystemTests.swift @@ -864,12 +864,11 @@ class FileSystemTests: XCTestCase { func testHasAttribute() throws { try withTemporaryDirectory(removeTreeOnDeinit: true) { tempDir in let filePath = tempDir.appending(component: "quarantined") - let attributeName = "com.apple.quarantine" try localFileSystem.writeFileContents(filePath, bytes: "") - try Process.checkNonZeroExit(args: "xattr", "-w", attributeName, "foo", filePath.pathString) - XCTAssertTrue(localFileSystem.hasAttribute(name: attributeName, filePath)) - try Process.checkNonZeroExit(args: "xattr", "-d", attributeName, filePath.pathString) - XCTAssertFalse(localFileSystem.hasAttribute(name: attributeName, filePath)) + try Process.checkNonZeroExit(args: "xattr", "-w", FileSystemAttribute.quarantine.rawValue, "foo", filePath.pathString) + XCTAssertTrue(localFileSystem.hasAttribute(.quarantine, filePath)) + try Process.checkNonZeroExit(args: "xattr", "-d", FileSystemAttribute.quarantine.rawValue, filePath.pathString) + XCTAssertFalse(localFileSystem.hasAttribute(.quarantine, filePath)) } } #endif From 35cb826d676b801718242973195a859066a99197 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 10 May 2023 15:47:42 +0100 Subject: [PATCH 6/7] Fix build on non-Darwin platforms due to raw value error --- Sources/TSCBasic/FileSystem.swift | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Sources/TSCBasic/FileSystem.swift b/Sources/TSCBasic/FileSystem.swift index d97529bf..d9a2f72d 100644 --- a/Sources/TSCBasic/FileSystem.swift +++ b/Sources/TSCBasic/FileSystem.swift @@ -138,10 +138,30 @@ public enum FileMode: Sendable { } /// Extended file system attributes that can applied to a given file path. See also ``FileSystem/hasAttribute(_:_:)``. -public enum FileSystemAttribute: String { +public enum FileSystemAttribute: RawRepresentable { #if canImport(Darwin) - case quarantine = "com.apple.quarantine" + case quarantine #endif + + public init?(rawValue: String) { + switch rawValue { + #if canImport(Darwin) + case "com.apple.quarantine": + self = .quarantine + #endif + default: + return nil + } + } + + public var rawValue: String { + switch self { + #if canImport(Darwin) + case .quarantine: + return "com.apple.quarantine" + #endif + } + } } // FIXME: Design an asynchronous story? From c36030df278549a7dd259b7d42ccac22637f2f3d Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 10 May 2023 17:06:56 +0100 Subject: [PATCH 7/7] Bring back the old function to fix build issues --- Sources/TSCBasic/FileSystem.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/TSCBasic/FileSystem.swift b/Sources/TSCBasic/FileSystem.swift index d9a2f72d..dde86e7c 100644 --- a/Sources/TSCBasic/FileSystem.swift +++ b/Sources/TSCBasic/FileSystem.swift @@ -195,6 +195,9 @@ public protocol FileSystem: AnyObject { /// Check whether the given path is accessible and writable. func isWritable(_ path: AbsolutePath) -> Bool + @available(*, deprecated, message: "use `hasAttribute(_:_:)` instead") + func hasQuarantineAttribute(_ path: AbsolutePath) -> Bool + /// Returns `true` if a given path has an attribute with a given name applied when file system supports this /// attribute. Returns `false` if such attribute is not applied or it isn't supported. func hasAttribute(_ name: FileSystemAttribute, _ path: AbsolutePath) -> Bool @@ -325,6 +328,8 @@ public extension FileSystem { throw FileSystemError(.unsupported, path) } + func hasQuarantineAttribute(_ path: AbsolutePath) -> Bool { false } + func hasAttribute(_ name: FileSystemAttribute, _ path: AbsolutePath) -> Bool { false } }