From 0d90f148104d4b713ec3c3262b0c68752838215b Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Thu, 11 Jul 2024 21:29:50 -0700 Subject: [PATCH 1/5] Implement additional windows file attributes --- .../FileManager/FileManager+Files.swift | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift index 5ab7f8808..63a69e9bd 100644 --- a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift +++ b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift @@ -553,22 +553,20 @@ extension _FileManagerImpl { func attributesOfItem(atPath path: String) throws -> [FileAttributeKey : Any] { #if os(Windows) return try path.withNTPathRepresentation { pwszPath in - var faAttributes: WIN32_FILE_ATTRIBUTE_DATA = .init() - guard GetFileAttributesExW(pwszPath, GetFileExInfoStandard, &faAttributes) else { - throw CocoaError.errorWithFilePath(path, win32: GetLastError(), reading: true) - } - let hFile = CreateFileW(pwszPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nil, OPEN_EXISTING, 0, nil) if hFile == INVALID_HANDLE_VALUE { throw CocoaError.errorWithFilePath(path, win32: GetLastError(), reading: true) } defer { CloseHandle(hFile) } + var info: BY_HANDLE_FILE_INFORMATION = BY_HANDLE_FILE_INFORMATION() + GetFileInformationByHandle(hFile, &info) + let dwFileType = GetFileType(hFile) let fatType: FileAttributeType = switch (dwFileType) { case FILE_TYPE_CHAR: FileAttributeType.typeCharacterSpecial case FILE_TYPE_DISK: - faAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY + info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY ? FileAttributeType.typeDirectory : FileAttributeType.typeRegular case FILE_TYPE_PIPE: FileAttributeType.typeSocket @@ -576,26 +574,40 @@ extension _FileManagerImpl { default: FileAttributeType.typeUnknown } - let size: UInt64 = (UInt64(faAttributes.nFileSizeHigh) << 32) | UInt64(faAttributes.nFileSizeLow) - let creation: Date = Date(timeIntervalSince1970: faAttributes.ftCreationTime.timeIntervalSince1970) - let modification: Date = Date(timeIntervalSince1970: faAttributes.ftLastWriteTime.timeIntervalSince1970) - return [ + let systemNumber = UInt64(info.dwVolumeSerialNumber) + let systemFileNumber = UInt64(info.nFileIndexHigh << 32) | UInt64(info.nFileIndexLow) + let referenceCount = UInt64(info.nNumberOfLinks) + + let isReadOnly = info.dwFileAttributes & FILE_ATTRIBUTE_READONLY != 0 + var posixPermissions = UInt16(_S_IEXEC | _S_IREAD) + if !isReadOnly { + posixPermissions |= UInt16(_S_IWRITE) + } + + let size: UInt64 = (UInt64(info.nFileSizeHigh) << 32) | UInt64(info.nFileSizeLow) + let creation: Date = Date(timeIntervalSince1970: info.ftCreationTime.timeIntervalSince1970) + let modification: Date = Date(timeIntervalSince1970: info.ftLastWriteTime.timeIntervalSince1970) + var result: [FileAttributeKey : Any] = [ .size: _writeFileAttributePrimitive(size, as: UInt.self), .modificationDate: modification, .creationDate: creation, .type: fatType, + .systemNumber: _writeFileAttributePrimitive(systemNumber, as: UInt.self), + .systemFileNumber: _writeFileAttributePrimitive(systemFileNumber, as: UInt.self), + .posixPermissions: _writeFileAttributePrimitive(posixPermissions, as: UInt.self), + .referenceCount: _writeFileAttributePrimitive(referenceCount, as: UInt.self), - // TODO(compnerd) support these attributes, remapping the Windows semantics... - // .posixPermissions: ..., - // .referenceCount: ..., - // .systemNumber: ..., - // .systemFileNumber: ..., - // .ownerAccountID: ..., - // .groupownerAccountID: ..., - // .ownerAccountName: ..., - // .groupOwnerAccountName: ..., - // .deviceIdentifier: ..., + // Uid is always 0 on Windows systems + .ownerAccountID: _writeFileAttributePrimitive(0, as: UInt.self), + + // Group id is always 0 on Windows + .groupOwnerAccountID: _writeFileAttributePrimitive(0, as: UInt.self) ] + + if fatType == .typeCharacterSpecial || fatType == .typeBlockSpecial { + result[.deviceIdentifier] = _writeFileAttributePrimitive(info.dwVolumeSerialNumber, as: UInt.self) + } + return result } #else try fileManager.withFileSystemRepresentation(for: path) { fsRep in From 24e8255240904b742c8af80029a1985b3c72cfb2 Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld <1004103+jmschonfeld@users.noreply.github.com> Date: Fri, 12 Jul 2024 09:01:40 -0700 Subject: [PATCH 2/5] Ensure GetFileInformationByHandle succeeds Co-authored-by: Saleem Abdulrasool --- .../FoundationEssentials/FileManager/FileManager+Files.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift index 63a69e9bd..e683a9f1c 100644 --- a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift +++ b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift @@ -560,7 +560,9 @@ extension _FileManagerImpl { defer { CloseHandle(hFile) } var info: BY_HANDLE_FILE_INFORMATION = BY_HANDLE_FILE_INFORMATION() - GetFileInformationByHandle(hFile, &info) + guard GetFileInformationByHandle(hFile, &info) else { + throw CocoaError.errorWithFilePath(path, win32: GetLastError(), reading: true) + } let dwFileType = GetFileType(hFile) let fatType: FileAttributeType = switch (dwFileType) { From 40456f0dba617021fa6ab5d58d2aa21b46a69acf Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Fri, 12 Jul 2024 09:18:47 -0700 Subject: [PATCH 3/5] Correctly determine executability of file --- .../FileManager/FileManager+Files.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift index e683a9f1c..1d6acdbb4 100644 --- a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift +++ b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift @@ -581,10 +581,18 @@ extension _FileManagerImpl { let referenceCount = UInt64(info.nNumberOfLinks) let isReadOnly = info.dwFileAttributes & FILE_ATTRIBUTE_READONLY != 0 - var posixPermissions = UInt16(_S_IEXEC | _S_IREAD) + let isExecutable = if fatType == .typeDirectory { + true // Directories are always considered executable + } else { + SaferiIsExecutableFileType(pwszPath, 0) + } + var posixPermissions = UInt16(_S_IREAD) if !isReadOnly { posixPermissions |= UInt16(_S_IWRITE) } + if isExecutable { + posixPermissions |= UInt16(_S_IEXEC) + } let size: UInt64 = (UInt64(info.nFileSizeHigh) << 32) | UInt64(info.nFileSizeLow) let creation: Date = Date(timeIntervalSince1970: info.ftCreationTime.timeIntervalSince1970) From 4f6695449b63705d334f182e367a246dedf5a701 Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Fri, 12 Jul 2024 09:20:36 -0700 Subject: [PATCH 4/5] Simplify executable check --- .../FileManager/FileManager+Files.swift | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift index 1d6acdbb4..321faec35 100644 --- a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift +++ b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift @@ -581,11 +581,8 @@ extension _FileManagerImpl { let referenceCount = UInt64(info.nNumberOfLinks) let isReadOnly = info.dwFileAttributes & FILE_ATTRIBUTE_READONLY != 0 - let isExecutable = if fatType == .typeDirectory { - true // Directories are always considered executable - } else { - SaferiIsExecutableFileType(pwszPath, 0) - } + // Directories are always considered executable, but we check for other types + let isExecutable = fatType == .typeDirectory || SaferiIsExecutableFileType(pwszPath, 0) var posixPermissions = UInt16(_S_IREAD) if !isReadOnly { posixPermissions |= UInt16(_S_IWRITE) From d958e81ba7a3b325fea3394224c3fef68970856d Mon Sep 17 00:00:00 2001 From: Jeremy Schonfeld Date: Fri, 12 Jul 2024 09:33:01 -0700 Subject: [PATCH 5/5] Make .deviceIdentifier a future TODO --- .../FileManager/FileManager+Files.swift | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift index 321faec35..4d078ec6c 100644 --- a/Sources/FoundationEssentials/FileManager/FileManager+Files.swift +++ b/Sources/FoundationEssentials/FileManager/FileManager+Files.swift @@ -594,7 +594,7 @@ extension _FileManagerImpl { let size: UInt64 = (UInt64(info.nFileSizeHigh) << 32) | UInt64(info.nFileSizeLow) let creation: Date = Date(timeIntervalSince1970: info.ftCreationTime.timeIntervalSince1970) let modification: Date = Date(timeIntervalSince1970: info.ftLastWriteTime.timeIntervalSince1970) - var result: [FileAttributeKey : Any] = [ + return [ .size: _writeFileAttributePrimitive(size, as: UInt.self), .modificationDate: modification, .creationDate: creation, @@ -609,12 +609,9 @@ extension _FileManagerImpl { // Group id is always 0 on Windows .groupOwnerAccountID: _writeFileAttributePrimitive(0, as: UInt.self) - ] - if fatType == .typeCharacterSpecial || fatType == .typeBlockSpecial { - result[.deviceIdentifier] = _writeFileAttributePrimitive(info.dwVolumeSerialNumber, as: UInt.self) - } - return result + // TODO: Support .deviceIdentifier + ] } #else try fileManager.withFileSystemRepresentation(for: path) { fsRep in