Skip to content

Commit 7f9238e

Browse files
[Windows] Implement additional file attributes (#730)
* Implement additional windows file attributes * Ensure GetFileInformationByHandle succeeds Co-authored-by: Saleem Abdulrasool <[email protected]> * Correctly determine executability of file * Simplify executable check * Make .deviceIdentifier a future TODO --------- Co-authored-by: Saleem Abdulrasool <[email protected]>
1 parent c3fc214 commit 7f9238e

File tree

1 file changed

+35
-19
lines changed

1 file changed

+35
-19
lines changed

Sources/FoundationEssentials/FileManager/FileManager+Files.swift

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -553,48 +553,64 @@ extension _FileManagerImpl {
553553
func attributesOfItem(atPath path: String) throws -> [FileAttributeKey : Any] {
554554
#if os(Windows)
555555
return try path.withNTPathRepresentation { pwszPath in
556-
var faAttributes: WIN32_FILE_ATTRIBUTE_DATA = .init()
557-
guard GetFileAttributesExW(pwszPath, GetFileExInfoStandard, &faAttributes) else {
558-
throw CocoaError.errorWithFilePath(path, win32: GetLastError(), reading: true)
559-
}
560-
561556
let hFile = CreateFileW(pwszPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nil, OPEN_EXISTING, 0, nil)
562557
if hFile == INVALID_HANDLE_VALUE {
563558
throw CocoaError.errorWithFilePath(path, win32: GetLastError(), reading: true)
564559
}
565560
defer { CloseHandle(hFile) }
566561

562+
var info: BY_HANDLE_FILE_INFORMATION = BY_HANDLE_FILE_INFORMATION()
563+
guard GetFileInformationByHandle(hFile, &info) else {
564+
throw CocoaError.errorWithFilePath(path, win32: GetLastError(), reading: true)
565+
}
566+
567567
let dwFileType = GetFileType(hFile)
568568
let fatType: FileAttributeType = switch (dwFileType) {
569569
case FILE_TYPE_CHAR: FileAttributeType.typeCharacterSpecial
570570
case FILE_TYPE_DISK:
571-
faAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY
571+
info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY
572572
? FileAttributeType.typeDirectory
573573
: FileAttributeType.typeRegular
574574
case FILE_TYPE_PIPE: FileAttributeType.typeSocket
575575
case FILE_TYPE_UNKNOWN: FileAttributeType.typeUnknown
576576
default: FileAttributeType.typeUnknown
577577
}
578578

579-
let size: UInt64 = (UInt64(faAttributes.nFileSizeHigh) << 32) | UInt64(faAttributes.nFileSizeLow)
580-
let creation: Date = Date(timeIntervalSince1970: faAttributes.ftCreationTime.timeIntervalSince1970)
581-
let modification: Date = Date(timeIntervalSince1970: faAttributes.ftLastWriteTime.timeIntervalSince1970)
579+
let systemNumber = UInt64(info.dwVolumeSerialNumber)
580+
let systemFileNumber = UInt64(info.nFileIndexHigh << 32) | UInt64(info.nFileIndexLow)
581+
let referenceCount = UInt64(info.nNumberOfLinks)
582+
583+
let isReadOnly = info.dwFileAttributes & FILE_ATTRIBUTE_READONLY != 0
584+
// Directories are always considered executable, but we check for other types
585+
let isExecutable = fatType == .typeDirectory || SaferiIsExecutableFileType(pwszPath, 0)
586+
var posixPermissions = UInt16(_S_IREAD)
587+
if !isReadOnly {
588+
posixPermissions |= UInt16(_S_IWRITE)
589+
}
590+
if isExecutable {
591+
posixPermissions |= UInt16(_S_IEXEC)
592+
}
593+
594+
let size: UInt64 = (UInt64(info.nFileSizeHigh) << 32) | UInt64(info.nFileSizeLow)
595+
let creation: Date = Date(timeIntervalSince1970: info.ftCreationTime.timeIntervalSince1970)
596+
let modification: Date = Date(timeIntervalSince1970: info.ftLastWriteTime.timeIntervalSince1970)
582597
return [
583598
.size: _writeFileAttributePrimitive(size, as: UInt.self),
584599
.modificationDate: modification,
585600
.creationDate: creation,
586601
.type: fatType,
602+
.systemNumber: _writeFileAttributePrimitive(systemNumber, as: UInt.self),
603+
.systemFileNumber: _writeFileAttributePrimitive(systemFileNumber, as: UInt.self),
604+
.posixPermissions: _writeFileAttributePrimitive(posixPermissions, as: UInt.self),
605+
.referenceCount: _writeFileAttributePrimitive(referenceCount, as: UInt.self),
606+
607+
// Uid is always 0 on Windows systems
608+
.ownerAccountID: _writeFileAttributePrimitive(0, as: UInt.self),
609+
610+
// Group id is always 0 on Windows
611+
.groupOwnerAccountID: _writeFileAttributePrimitive(0, as: UInt.self)
587612

588-
// TODO(compnerd) support these attributes, remapping the Windows semantics...
589-
// .posixPermissions: ...,
590-
// .referenceCount: ...,
591-
// .systemNumber: ...,
592-
// .systemFileNumber: ...,
593-
// .ownerAccountID: ...,
594-
// .groupownerAccountID: ...,
595-
// .ownerAccountName: ...,
596-
// .groupOwnerAccountName: ...,
597-
// .deviceIdentifier: ...,
613+
// TODO: Support .deviceIdentifier
598614
]
599615
}
600616
#else

0 commit comments

Comments
 (0)