@@ -908,6 +908,91 @@ enum _FileOperations {
908
908
#endif
909
909
}
910
910
#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
+ }
911
996
912
997
#if os(WASI)
913
998
private static func _linkOrCopyFile( _ srcPtr: UnsafePointer < CChar > , _ dstPtr: UnsafePointer < CChar > , with fileManager: FileManager , delegate: some LinkOrCopyDelegate ) throws {
@@ -1000,18 +1085,7 @@ enum _FileOperations {
1000
1085
1001
1086
case FTS_DP:
1002
1087
// 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)
1015
1089
1016
1090
case FTS_SL: fallthrough // Symlink.
1017
1091
case FTS_SLNONE: // Symlink with no target.
0 commit comments