Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
ahoppen opened this issue Nov 16, 2024 · 4 comments
Assignees
Labels

Comments

@ahoppen
Copy link
Member

ahoppen commented Nov 16, 2024

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

  • Skip the files that exceed the maximum path length but continue iteration afterwards or
  • Return all files even those that exceed the maximum path length

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

ahoppen referenced this issue in ahoppen/sourcekit-lsp Nov 16, 2024
It appears that moving the index-build directory to be a subdirectory of `.build` caused some file in the index-build directory to exceed the `MAX_PATH` length on the SourceKit-LSP PR testing job (but not the swift PR job because that has a shorter job name). Because of https://github.com/swiftlang/swift-foundation/issues/1049, we would stop directory iteration at that file exceeding `MAX_PATH` and never find `MyDependency.swift`, causing these tests to fail.
ahoppen referenced this issue in ahoppen/sourcekit-lsp Nov 16, 2024
It appears that moving the index-build directory to be a subdirectory of `.build` caused some file in the index-build directory to exceed the `MAX_PATH` length on the SourceKit-LSP PR testing job (but not the swift PR job because that has a shorter job name). Because of https://github.com/swiftlang/swift-foundation/issues/1049, we would stop directory iteration at that file exceeding `MAX_PATH` and never find `MyDependency.swift`, causing these tests to fail.
@jmschonfeld jmschonfeld transferred this issue from swiftlang/swift-foundation Nov 18, 2024
@jmschonfeld jmschonfeld self-assigned this Nov 18, 2024
@jmschonfeld
Copy link
Contributor

jmschonfeld commented Nov 19, 2024

@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:

guard let handler = _errorHandler else { return nil }
if !handler(url, _NSErrorWithWindowsError(GetLastError(), reading: true, paths: [url.path])) {
return nil
}

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
Copy link
Member Author

ahoppen commented Nov 19, 2024

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
Copy link
Contributor

The first part of the issue (the default error handler behavior on Windows) will be resolved by #5136

@ahoppen
Copy link
Member Author

ahoppen commented Nov 20, 2024

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants