Skip to content

Commit ee5f0c0

Browse files
committed
Foundation: further adjust the _removeItem for large paths
This function explicitly requires to be iterative to ensure that we do not stack overflow on large directories. Rewrite the logic for avoiding some unnecessary re-encodings and disk IOs. The failure in the removal was identified by DocC.
1 parent 5ee4aee commit ee5f0c0

File tree

1 file changed

+59
-65
lines changed

1 file changed

+59
-65
lines changed

Diff for: Sources/Foundation/FileManager+Win32.swift

+59-65
Original file line numberDiff line numberDiff line change
@@ -582,83 +582,77 @@ extension FileManager {
582582
return
583583
}
584584

585-
let faAttributes: WIN32_FILE_ATTRIBUTE_DATA
586-
do {
587-
faAttributes = try windowsFileAttributes(atPath: path)
588-
} catch {
589-
// removeItem on POSIX throws fileNoSuchFile rather than
590-
// fileReadNoSuchFile that windowsFileAttributes will
591-
// throw if it doesn't find the file.
592-
if (error as NSError).code == CocoaError.fileReadNoSuchFile.rawValue {
593-
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
594-
} else {
595-
throw error
596-
}
597-
}
598-
599-
if faAttributes.dwFileAttributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY {
600-
if try !FileManager.default._fileSystemRepresentation(withPath: path, {
601-
SetFileAttributesW($0, faAttributes.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY)
602-
}) {
603-
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
604-
}
605-
}
585+
try URL(fileURLWithPath: path).withUnsafeFileSystemRepresentation {
586+
"\\\\?\\\(String(cString: $0!))".withCString(encodedAs: UTF16.self) {
587+
var faAttributes: WIN32_FILE_ATTRIBUTE_DATA = .init()
588+
if !GetFileAttributesExW($0, GetFileExInfoStandard, &faAttributes) {
589+
throw _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [path])
590+
}
606591

607-
if faAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == 0 {
608-
if try !FileManager.default._fileSystemRepresentation(withPath: path, DeleteFileW) {
609-
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
610-
}
611-
return
612-
}
592+
guard faAttributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY else {
593+
if faAttributes.dwFileAttributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY {
594+
if !SetFileAttributesW($0, faAttributes.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY) {
595+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
596+
}
597+
}
613598

614-
var dirStack = [path]
615-
var itemPath = ""
616-
while let currentDir = dirStack.popLast() {
617-
do {
618-
itemPath = currentDir
619-
guard alreadyConfirmed || shouldRemoveItemAtPath(itemPath, isURL: isURL) else {
620-
continue
621-
}
599+
if !DeleteFileW($0) {
600+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [path])
601+
}
622602

623-
if try FileManager.default._fileSystemRepresentation(withPath: itemPath, RemoveDirectoryW) {
624-
continue
603+
return
625604
}
626-
guard GetLastError() == ERROR_DIR_NOT_EMPTY else {
627-
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [itemPath])
628-
}
629-
dirStack.append(itemPath)
630605

631-
let root = URL(fileURLWithPath: itemPath, isDirectory: true)
632-
try walk(directory: root) { (entry, attributes) in
633-
if entry == "." || entry == ".." { return }
634-
635-
let isDirectory = attributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY && attributes & FILE_ATTRIBUTE_REPARSE_POINT == 0
636-
let path = root.appendingPathComponent(entry, isDirectory: isDirectory)
637-
if isDirectory {
638-
dirStack.append(path.path)
639-
} else {
640-
path.withUnsafeFileSystemRepresentation {
641-
guard alreadyConfirmed || shouldRemoveItemAtPath(path.path, isURL: isURL) else {
642-
return
643-
}
606+
var stack: [String] = [path]
607+
while let directory = stack.popLast() {
608+
do {
609+
guard alreadyConfirmed || shouldRemoveItemAtPath(directory, isURL: isURL) else {
610+
continue
611+
}
644612

645-
"\\\\?\\\(String(cString: $0!))".withCString(encodedAs: UTF16.self) {
646-
if attributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY {
647-
if !SetFileAttributesW($0, attributes & ~FILE_ATTRIBUTE_READONLY) {
648-
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [entry])
649-
}
613+
let root = URL(fileURLWithPath: directory, isDirectory: true)
614+
try root.withUnsafeFileSystemRepresentation {
615+
try "\\\\?\\\(String(cString: $0!))".withCString(encodedAs: UTF16.self) {
616+
if !RemoveDirectoryW($0) { continue }
617+
guard GetLastError() == ERROR_DIR_NOT_EMPTY else {
618+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [directory])
650619
}
651-
if !DeleteFileW($0) {
652-
throw _NSErrorWithWindowsError(GeteLastErrro(), reading: false, paths: [entry])
620+
stack.append(root.path)
621+
622+
try walk(directory: root) { (entry, attributes) in
623+
if entry == "." || entry == ".." { return }
624+
625+
let isDirectory = attributes & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY && attributes & FILE_ATTRIBUTE_REPARSE_POINT == 0
626+
let path = root.appendingPathComponent(entry, isDirectory: isDirectory)
627+
if isDirectory {
628+
stack.append(path.path)
629+
} else {
630+
guard alreadyConfirmed || shouldRemoveItemAtPath(path.path, isURL: isURL) else {
631+
return
632+
}
633+
634+
path.withUnsafeFileSystemRepresentation {
635+
"\\\\?\\\(String(cString: $0!))".withCString(encodedAs: UTF16.self) {
636+
if attributes & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY {
637+
if !SetFileAttributesW($0, attributes & ~FILE_ATTRIBUTE_READONLY) {
638+
throw _NSErrorWithWindowsError(GetLastError(), reading: false, paths: [entry])
639+
}
640+
}
641+
if !DeleteFileW($0) {
642+
throw _NSErrorWithWindowsError(GeteLastErrro(), reading: false, paths: [entry])
643+
}
644+
}
645+
}
646+
}
653647
}
654648
}
655649
}
650+
} catch let error {
651+
if !shouldProceedAfterError(error, removingItemAtPath: directory, isURL: isURL) {
652+
throw error
653+
}
656654
}
657655
}
658-
} catch {
659-
if !shouldProceedAfterError(error, removingItemAtPath: itemPath, isURL: isURL) {
660-
throw error
661-
}
662656
}
663657
}
664658
}

0 commit comments

Comments
 (0)