@@ -908,6 +908,91 @@ enum _FileOperations {
908908 #endif
909909 }
910910 #endif
911+
912+ #if !canImport(Darwin)
913+ private static func _copyDirectoryMetadata( srcFD: CInt , srcPath: @autoclosure ( ) -> String , dstFD: CInt , dstPath: @autoclosure ( ) -> String , delegate: some LinkOrCopyDelegate ) throws {
914+ // Copy extended attributes
915+ var size = flistxattr ( srcFD, nil , 0 )
916+ if size > 0 {
917+ try withUnsafeTemporaryAllocation ( of: CChar . self, capacity: size) { keyList in
918+ size = flistxattr ( srcFD, keyList. baseAddress!, size)
919+ if size > 0 {
920+ var current = keyList. baseAddress!
921+ let end = keyList. baseAddress!. advanced ( by: keyList. count)
922+ while current < end {
923+ var valueSize = fgetxattr ( srcFD, current, nil , 0 )
924+ if valueSize >= 0 {
925+ try withUnsafeTemporaryAllocation ( of: UInt8 . self, capacity: valueSize) { valueBuffer in
926+ valueSize = fgetxattr ( srcFD, current, valueBuffer. baseAddress!, valueSize)
927+ if valueSize >= 0 {
928+ if fsetxattr ( dstFD, current, valueBuffer. baseAddress!, valueSize, 0 ) != 0 {
929+ try delegate. throwIfNecessary ( errno, srcPath ( ) , dstPath ( ) )
930+ }
931+ }
932+ }
933+ }
934+ current = current. advanced ( by: strlen ( current) + 1 ) /* pass null byte */
935+ }
936+ }
937+ }
938+ }
939+ var statInfo = stat ( )
940+ if fstat ( srcFD, & statInfo) == 0 {
941+ // Copy owner/group
942+ if fchown ( dstFD, statInfo. st_uid, statInfo. st_gid) != 0 {
943+ try delegate. throwIfNecessary ( errno, srcPath ( ) , dstPath ( ) )
944+ }
945+
946+ // Copy modification date
947+ let value = timeval ( tv_sec: statInfo. st_mtim. tv_sec, tv_usec: statInfo. st_mtim. tv_nsec / 1000 )
948+ var tv = ( value, value)
949+ try withUnsafePointer ( to: & tv) {
950+ try $0. withMemoryRebound ( to: timeval. self, capacity: 2 ) {
951+ if futimes ( dstFD, $0) != 0 {
952+ try delegate. throwIfNecessary ( errno, srcPath ( ) , dstPath ( ) )
953+ }
954+ }
955+ }
956+
957+ // Copy permissions
958+ if fchmod ( dstFD, statInfo. st_mode) != 0 {
959+ try delegate. throwIfNecessary ( errno, srcPath ( ) , dstPath ( ) )
960+ }
961+ } else {
962+ try delegate. throwIfNecessary ( errno, srcPath ( ) , dstPath ( ) )
963+ }
964+ }
965+ #endif
966+
967+ private static func _openDirectoryFD( _ ptr: UnsafePointer < CChar > , srcPath: @autoclosure ( ) -> String , dstPath: @autoclosure ( ) -> String , delegate: some LinkOrCopyDelegate ) throws -> CInt ? {
968+ let fd = open ( ptr, O_RDONLY | O_NOFOLLOW | O_DIRECTORY)
969+ guard fd >= 0 else {
970+ try delegate. throwIfNecessary ( errno, srcPath ( ) , dstPath ( ) )
971+ return nil
972+ }
973+ return fd
974+ }
975+
976+ // Safely copies metadata from one directory to another ensuring that both paths are directories and cannot be swapped for files before/while copying metadata
977+ private static func _safeCopyDirectoryMetadata( src: UnsafePointer < CChar > , dst: UnsafePointer < CChar > , delegate: some LinkOrCopyDelegate , extraFlags: Int32 = 0 ) throws {
978+ guard let srcFD = try _openDirectoryFD ( src, srcPath: String ( cString: src) , dstPath: String ( cString: dst) , delegate: delegate) else {
979+ return
980+ }
981+ defer { close ( srcFD) }
982+
983+ guard let dstFD = try _openDirectoryFD ( dst, srcPath: String ( cString: src) , dstPath: String ( cString: dst) , delegate: delegate) else {
984+ return
985+ }
986+ defer { close ( dstFD) }
987+
988+ #if canImport(Darwin)
989+ if fcopyfile ( srcFD, dstFD, nil , copyfile_flags_t ( COPYFILE_METADATA | COPYFILE_NOFOLLOW | extraFlags) ) != 0 {
990+ try delegate. throwIfNecessary ( errno, String ( cString: src) , String ( cString: dst) )
991+ }
992+ #else
993+ try _copyDirectoryMetadata ( srcFD: srcFD, srcPath: String ( cString: src) , dstFD: dstFD, dstPath: String ( cString: dst) , delegate: delegate)
994+ #endif
995+ }
911996
912997 #if os(WASI)
913998 private static func _linkOrCopyFile( _ srcPtr: UnsafePointer < CChar > , _ dstPtr: UnsafePointer < CChar > , with fileManager: FileManager , delegate: some LinkOrCopyDelegate ) throws {
@@ -1000,18 +1085,7 @@ enum _FileOperations {
10001085
10011086 case FTS_DP:
10021087 // Directory being visited in post-order - copy the permissions over.
1003- #if canImport(Darwin)
1004- if copyfile ( fts_path, buffer. baseAddress!, nil , copyfile_flags_t ( COPYFILE_METADATA | COPYFILE_NOFOLLOW | extraFlags) ) != 0 {
1005- try delegate. throwIfNecessary ( errno, String ( cString: fts_path) , String ( cString: buffer. baseAddress!) )
1006- }
1007- #else
1008- do {
1009- let attributes = try fileManager. attributesOfItem ( atPath: String ( cString: fts_path) )
1010- try fileManager. setAttributes ( attributes, ofItemAtPath: String ( cString: buffer. baseAddress!) )
1011- } catch {
1012- try delegate. throwIfNecessary ( error, String ( cString: fts_path) , String ( cString: buffer. baseAddress!) )
1013- }
1014- #endif
1088+ try Self . _safeCopyDirectoryMetadata ( src: fts_path, dst: buffer. baseAddress!, delegate: delegate, extraFlags: extraFlags)
10151089
10161090 case FTS_SL: fallthrough // Symlink.
10171091 case FTS_SLNONE: // Symlink with no target.
0 commit comments