swiftlang / swift-corelibs-foundation

The Foundation Project, providing core utilities, internationalization, and OS independence
swift.org
Apache License 2.0
5.29k stars 1.13k forks source link

`FileManager.enumerator(at:)` stops iterating upon seeing path that exceeds `MAX_PATH` on Windows #5135

Open ahoppen opened 1 week ago

ahoppen commented 1 week ago

Run the following to create a file at a path that exceeds MAXPATH

cd $HOME\Desktop
mkdir directoryTest
cd directoryTest
echo a > a.txt
echo a > z.txt
mkdir eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
subst Z: .\eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee\
Z:
echo a > eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee.txt

Now run

import Foundation

let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: #"C:\Users\alex\Desktop\directoryTest"#), includingPropertiesForKeys: nil)
while let url = enumerator?.nextObject() as? URL {
  print(url)
}

Notice how it outputs

file:///C:/Users/alex/Desktop/directoryTest/z.txt
file:///C:/Users/alex/Desktop/directoryTest/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/

Directory iteration stops upon reaching the eeeee…txt file that exceeds MAX_PATH and a.txt is never printed. I would expect directory iteration to either

but not to stop iteration upon reaching the first file that exceeds the maximum path length altogether.

jmschonfeld commented 4 days ago

@ahoppen the reason that enumeration stops here is because an error has occurred. In particular, the error is:

Error Domain=NSCocoaErrorDomain Code=260 "The file doesn’t exist." on file:///C:/Users/jmschonfeld/Desktop/testDir/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee.txt

I suspect some API which we are calling is not handling the long paths well. The documentation states that, for the error handler parameter:

If you specify nil for this parameter, the enumerator object continues to enumerate items as if you had specified a block that returned true.

However it seems like on Windows that behavior is the opposite:

https://github.com/swiftlang/swift-corelibs-foundation/blob/21b3196b33a64d53a0989881fc9a486227b4a316/Sources/Foundation/FileManager%2BWin32.swift#L326-L329

So I agree it seems like there's two problems here:

  1. The default (nil) error handler is the opposite of what is documented (we should confirm what is documented matches Darwin behavior)
  2. We error out while handling paths that are beyond the max path length. I'm not sure if that can be fixed, we'll have to see if Windows has APIs we can use to work around this, but it's worth investigating.

For now if you need to work around issue number 1, you can provide a custom error handler which returns true

ahoppen commented 4 days ago

Oh, great. Thanks for the detailed analysis. We’re not seeing any long path related issues right now because I shortened all paths sufficiently but I’ll use that workaround when we see an issue next time.

jmschonfeld commented 3 days ago

The first part of the issue (the default error handler behavior on Windows) will be resolved by https://github.com/swiftlang/swift-corelibs-foundation/pull/5136

ahoppen commented 2 days ago

Thanks for the fix @jmschonfeld. That was the issue that bogged me more than skipping directories that exceed MAX_PATH (which is somewhat understandable, I think).