Skip to content

Commit fe86c19

Browse files
committed
FileManager: Reduce memory usage of copyItem(atPath:toPath:)
- Enumerate a directory directly instead of using subpathsOfDirectory(atPath:) and creating an array. - When copying a regular file, use a buffer sized to st_blksize instead of doing a whole file read/write.
1 parent 57e5f7f commit fe86c19

File tree

1 file changed

+84
-8
lines changed

1 file changed

+84
-8
lines changed

Foundation/FileManager.swift

+84-8
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,81 @@ open class FileManager : NSObject {
434434

435435
return self.string(withFileSystemRepresentation: buf, length: Int(len))
436436
}
437-
437+
438+
private func _readFrom(fd: Int32, toBuffer buffer: UnsafeMutablePointer<UInt8>, length bytesToRead: Int, filename: String) throws -> Int {
439+
var bytesRead = 0
440+
441+
repeat {
442+
bytesRead = read(fd, buffer, bytesToRead)
443+
} while bytesRead < 0 && errno == EINTR
444+
guard bytesRead >= 0 else {
445+
throw _NSErrorWithErrno(errno, reading: true, path: filename)
446+
}
447+
return bytesRead
448+
}
449+
450+
private func _writeTo(fd: Int32, fromBuffer buffer : UnsafeMutablePointer<UInt8>, length bytesToWrite: Int, filename: String) throws {
451+
var bytesWritten = 0
452+
while bytesWritten < bytesToWrite {
453+
var written = 0
454+
let bytesLeftToWrite = bytesToWrite - bytesWritten
455+
repeat {
456+
written = write(fd, buffer.advanced(by: bytesWritten), bytesLeftToWrite)
457+
} while written < 0 && errno == EINTR
458+
guard written >= 0 else {
459+
throw _NSErrorWithErrno(errno, reading: false, path: filename)
460+
}
461+
bytesWritten += written
462+
}
463+
}
464+
465+
private func _copyRegularFile(atPath srcPath: String, toPath dstPath: String) throws {
466+
let srcRep = fileSystemRepresentation(withPath: srcPath)
467+
let dstRep = fileSystemRepresentation(withPath: dstPath)
468+
defer {
469+
srcRep.deallocate()
470+
dstRep.deallocate()
471+
}
472+
473+
var fileInfo = stat()
474+
guard stat(srcRep, &fileInfo) >= 0 else {
475+
throw _NSErrorWithErrno(errno, reading: true, path: srcPath)
476+
}
477+
478+
let srcfd = open(srcRep, O_RDONLY)
479+
guard srcfd >= 0 else {
480+
throw _NSErrorWithErrno(errno, reading: true, path: srcPath)
481+
}
482+
defer { close(srcfd) }
483+
484+
let dstfd = open(dstRep, O_WRONLY | O_CREAT | O_TRUNC, 0o666)
485+
guard dstfd >= 0 else {
486+
throw _NSErrorWithErrno(errno, reading: false, path: dstPath)
487+
}
488+
defer { close(dstfd) }
489+
490+
if fileInfo.st_size == 0 {
491+
// no copying required
492+
return
493+
}
494+
495+
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(fileInfo.st_blksize))
496+
defer { buffer.deallocate() }
497+
498+
var bytesRemaining = Int64(fileInfo.st_size)
499+
while bytesRemaining > 0 {
500+
let bytesToRead = min(bytesRemaining, Int64(fileInfo.st_blksize))
501+
let bytesRead = try _readFrom(fd: srcfd, toBuffer: buffer, length: Int(bytesToRead), filename: srcPath)
502+
if bytesRead == 0 {
503+
// Early EOF
504+
return
505+
}
506+
try _writeTo(fd: dstfd, fromBuffer: buffer, length: bytesRead, filename: dstPath)
507+
bytesRemaining -= Int64(bytesRead)
508+
}
509+
}
510+
511+
438512
open func copyItem(atPath srcPath: String, toPath dstPath: String) throws {
439513
guard
440514
let attrs = try? attributesOfItem(atPath: srcPath),
@@ -448,18 +522,20 @@ open class FileManager : NSObject {
448522
let destination = try destinationOfSymbolicLink(atPath: srcPath)
449523
try createSymbolicLink(atPath: dstPath, withDestinationPath: destination)
450524
} else if fileType == .typeRegular {
451-
if createFile(atPath: dstPath, contents: contents(atPath: srcPath), attributes: nil) == false {
452-
throw NSError(domain: NSCocoaErrorDomain, code: CocoaError.fileWriteUnknown.rawValue, userInfo: [NSFilePathErrorKey : NSString(string: dstPath)])
453-
}
525+
try _copyRegularFile(atPath: srcPath, toPath: dstPath)
454526
}
455527
}
456528

457529
if fileType == .typeDirectory {
458530
try createDirectory(atPath: dstPath, withIntermediateDirectories: false, attributes: nil)
459-
let subpaths = try subpathsOfDirectory(atPath: srcPath)
460-
for subpath in subpaths {
461-
let src = srcPath + "/" + subpath
462-
let dst = dstPath + "/" + subpath
531+
532+
guard let enumerator = enumerator(atPath: srcPath) else {
533+
throw _NSErrorWithErrno(ENOENT, reading: true, path: srcPath)
534+
}
535+
536+
while let item = enumerator.nextObject() as? String {
537+
let src = srcPath + "/" + item
538+
let dst = dstPath + "/" + item
463539
if let attrs = try? attributesOfItem(atPath: src), let fileType = attrs[.type] as? FileAttributeType {
464540
if fileType == .typeDirectory {
465541
try createDirectory(atPath: dst, withIntermediateDirectories: false, attributes: nil)

0 commit comments

Comments
 (0)