@@ -1046,6 +1046,43 @@ private struct FileManagerTests {
10461046 }
10471047 }
10481048
1049+ @Test func extendedAttributesDoNotFollowSymlinksWhenSetting( ) async throws {
1050+ #if os(Windows) || os(WASI) || os(OpenBSD) || canImport(Android)
1051+ throw Skip ( " Extended attributes not supported on this platform " )
1052+ #else
1053+ let xattrKey = FileAttributeKey ( " NSFileExtendedAttributes " )
1054+ #if canImport(Darwin)
1055+ let attrName = " com.swiftfoundation.symlinktest "
1056+ #elseif os(Linux)
1057+ // Linux requires the user.* namespace prefix for regular files
1058+ let attrName = " user.swiftfoundation.symlinktest "
1059+ #else
1060+ let attrName = " swiftfoundation.symlinktest "
1061+ #endif
1062+ let attrValue = Data ( [ 0xAA , 0xBB , 0xCC ] )
1063+
1064+ try await FilePlayground {
1065+ File ( " target " , contents: Data ( " payload " . utf8) )
1066+ SymbolicLink ( " link " , destination: " target " )
1067+ } . test { fileManager in
1068+ do {
1069+ try fileManager. setAttributes ( [ xattrKey: [ attrName: attrValue] ] , ofItemAtPath: " link " )
1070+ } catch let error as CocoaError where error. code == . featureUnsupported {
1071+ throw Skip ( " Extended attributes not supported on this filesystem: \( error) " )
1072+ }
1073+
1074+ let linkAttrs = try fileManager. attributesOfItem ( atPath: " link " )
1075+ let targetAttrs = try fileManager. attributesOfItem ( atPath: " target " )
1076+
1077+ let linkXattrs = linkAttrs [ xattrKey] as? [ String : Data ]
1078+ let targetXattrs = targetAttrs [ xattrKey] as? [ String : Data ]
1079+
1080+ #expect( linkXattrs ? [ attrName] == attrValue, " xattr should be applied to the symlink itself " )
1081+ #expect( targetXattrs ? [ attrName] == nil , " setAttributes must not follow symlinks when setting extended attributes " )
1082+ }
1083+ #endif
1084+ }
1085+
10491086 #if !canImport(Darwin) || os(macOS)
10501087 @Test func currentUserHomeDirectory( ) async throws {
10511088 let userName = ProcessInfo . processInfo. userName
0 commit comments