@@ -948,6 +948,91 @@ enum _FileOperations {
948
948
#endif
949
949
}
950
950
#endif
951
+
952
+ #if !canImport(Darwin)
953
+ private static func _copyDirectoryMetadata( srcFD: CInt , srcPath: @autoclosure ( ) -> String , dstFD: CInt , dstPath: @autoclosure ( ) -> String , delegate: some LinkOrCopyDelegate ) throws {
954
+ // Copy extended attributes
955
+ var size = flistxattr ( srcFD, nil , 0 )
956
+ if size > 0 {
957
+ try withUnsafeTemporaryAllocation ( of: CChar . self, capacity: size) { keyList in
958
+ size = flistxattr ( srcFD, keyList. baseAddress!, size)
959
+ if size > 0 {
960
+ var current = keyList. baseAddress!
961
+ let end = keyList. baseAddress!. advanced ( by: keyList. count)
962
+ while current < end {
963
+ var valueSize = fgetxattr ( srcFD, current, nil , 0 )
964
+ if valueSize >= 0 {
965
+ try withUnsafeTemporaryAllocation ( of: UInt8 . self, capacity: valueSize) { valueBuffer in
966
+ valueSize = fgetxattr ( srcFD, current, valueBuffer. baseAddress!, valueSize)
967
+ if valueSize >= 0 {
968
+ if fsetxattr ( dstFD, current, valueBuffer. baseAddress!, valueSize, 0 ) != 0 {
969
+ try delegate. throwIfNecessary ( errno, srcPath ( ) , dstPath ( ) )
970
+ }
971
+ }
972
+ }
973
+ }
974
+ current = current. advanced ( by: strlen ( current) + 1 ) /* pass null byte */
975
+ }
976
+ }
977
+ }
978
+ }
979
+ var statInfo = stat ( )
980
+ if fstat ( srcFD, & statInfo) == 0 {
981
+ // Copy owner/group
982
+ if fchown ( dstFD, statInfo. st_uid, statInfo. st_gid) != 0 {
983
+ try delegate. throwIfNecessary ( errno, srcPath ( ) , dstPath ( ) )
984
+ }
985
+
986
+ // Copy modification date
987
+ let value = timeval ( tv_sec: statInfo. st_mtim. tv_sec, tv_usec: statInfo. st_mtim. tv_nsec / 1000 )
988
+ var tv = ( value, value)
989
+ try withUnsafePointer ( to: & tv) {
990
+ try $0. withMemoryRebound ( to: timeval. self, capacity: 2 ) {
991
+ if futimes ( dstFD, $0) != 0 {
992
+ try delegate. throwIfNecessary ( errno, srcPath ( ) , dstPath ( ) )
993
+ }
994
+ }
995
+ }
996
+
997
+ // Copy permissions
998
+ if fchmod ( dstFD, statInfo. st_mode) != 0 {
999
+ try delegate. throwIfNecessary ( errno, srcPath ( ) , dstPath ( ) )
1000
+ }
1001
+ } else {
1002
+ try delegate. throwIfNecessary ( errno, srcPath ( ) , dstPath ( ) )
1003
+ }
1004
+ }
1005
+ #endif
1006
+
1007
+ private static func _openDirectoryFD( _ ptr: UnsafePointer < CChar > , srcPath: @autoclosure ( ) -> String , dstPath: @autoclosure ( ) -> String , delegate: some LinkOrCopyDelegate ) throws -> CInt ? {
1008
+ let fd = open ( ptr, O_RDONLY | O_NOFOLLOW | O_DIRECTORY)
1009
+ guard fd >= 0 else {
1010
+ try delegate. throwIfNecessary ( errno, srcPath ( ) , dstPath ( ) )
1011
+ return nil
1012
+ }
1013
+ return fd
1014
+ }
1015
+
1016
+ // Safely copies metadata from one directory to another ensuring that both paths are directories and cannot be swapped for files before/while copying metadata
1017
+ private static func _safeCopyDirectoryMetadata( src: UnsafePointer < CChar > , dst: UnsafePointer < CChar > , delegate: some LinkOrCopyDelegate , extraFlags: Int32 = 0 ) throws {
1018
+ guard let srcFD = try _openDirectoryFD ( src, srcPath: String ( cString: src) , dstPath: String ( cString: dst) , delegate: delegate) else {
1019
+ return
1020
+ }
1021
+ defer { close ( srcFD) }
1022
+
1023
+ guard let dstFD = try _openDirectoryFD ( dst, srcPath: String ( cString: src) , dstPath: String ( cString: dst) , delegate: delegate) else {
1024
+ return
1025
+ }
1026
+ defer { close ( dstFD) }
1027
+
1028
+ #if canImport(Darwin)
1029
+ if fcopyfile ( srcFD, dstFD, nil , copyfile_flags_t ( COPYFILE_METADATA | COPYFILE_NOFOLLOW | extraFlags) ) != 0 {
1030
+ try delegate. throwIfNecessary ( errno, String ( cString: src) , String ( cString: dst) )
1031
+ }
1032
+ #else
1033
+ try _copyDirectoryMetadata ( srcFD: srcFD, srcPath: String ( cString: src) , dstFD: dstFD, dstPath: String ( cString: dst) , delegate: delegate)
1034
+ #endif
1035
+ }
951
1036
952
1037
#if os(WASI)
953
1038
private static func _linkOrCopyFile( _ srcPtr: UnsafePointer < CChar > , _ dstPtr: UnsafePointer < CChar > , with fileManager: FileManager , delegate: some LinkOrCopyDelegate ) throws {
@@ -1040,18 +1125,7 @@ enum _FileOperations {
1040
1125
1041
1126
case FTS_DP:
1042
1127
// Directory being visited in post-order - copy the permissions over.
1043
- #if canImport(Darwin)
1044
- if copyfile ( fts_path, buffer. baseAddress!, nil , copyfile_flags_t ( COPYFILE_METADATA | COPYFILE_NOFOLLOW | extraFlags) ) != 0 {
1045
- try delegate. throwIfNecessary ( errno, String ( cString: fts_path) , String ( cString: buffer. baseAddress!) )
1046
- }
1047
- #else
1048
- do {
1049
- let attributes = try fileManager. attributesOfItem ( atPath: String ( cString: fts_path) )
1050
- try fileManager. setAttributes ( attributes, ofItemAtPath: String ( cString: buffer. baseAddress!) )
1051
- } catch {
1052
- try delegate. throwIfNecessary ( error, String ( cString: fts_path) , String ( cString: buffer. baseAddress!) )
1053
- }
1054
- #endif
1128
+ try Self . _safeCopyDirectoryMetadata ( src: fts_path, dst: buffer. baseAddress!, delegate: delegate, extraFlags: extraFlags)
1055
1129
1056
1130
case FTS_SL: fallthrough // Symlink.
1057
1131
case FTS_SLNONE: // Symlink with no target.
0 commit comments