Skip to content

Conversation

daveinglis
Copy link
Contributor

@daveinglis daveinglis commented Aug 22, 2025

The //?/ prefix needs to be added to directories if the length is greater than MAX_PATH - 12, however the PATHCCH_ALLOW_LONG_PATHS to PathAllocCanonicalize would only do that if the path was greater than MAX_PATH. This change uses PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, which forces it on all the time. Just needed to remove the prefix in a few places as it was making it way out into tests as this is suppose to be transparent to the user.

This also fixes createDirectory() withIntermediateDirectories: true for long paths on Windows.

closes #1486

@daveinglis
Copy link
Contributor Author

@swift-ci please test

@daveinglis daveinglis force-pushed the fix_long_window_paths branch from d5ffb8a to 1bf3406 Compare August 25, 2025 16:59
@daveinglis
Copy link
Contributor Author

@swift-ci please test

@daveinglis daveinglis force-pushed the fix_long_window_paths branch from 1bf3406 to 9192a96 Compare August 27, 2025 17:54
@daveinglis
Copy link
Contributor Author

@swift-ci please test

func createDirectoryRecursively(at directoryPath: String) throws {
// Check if directory already exists
var isDirectory: Bool = false
if fileExists(atPath: directoryPath, isDirectory: &isDirectory) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could open us up to TOC-TOU issues. Since we're already handling ERROR_ALREADY_EXISTS below when we try to create the directory, can we just elide this check and let the error handling of CreateDirectoryW take care of the rest? (I know the error handling below also feels TOC-TOU-like, but given we can't know if the pre-existing result was a directory in the same call, it seems better to have a TOC-TOU issue when deciding whether to throw the error than deciding whether to create the directory to begin with)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, that makes sense, will update.

Copy link
Contributor Author

@daveinglis daveinglis Aug 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so Im rethinking this.... This first check is recursively walking the parent looking for where to start creating the folders, without it, it would always walk to the root, creating the folders from there, this seems it could be quite the performance hit if this first check is removed, would it not?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous implementation didn't have this check, right (just confirming it's not a performance regression from a previous release to remove it)? I agree it would be more work, but it seems like that's probably correct work to do since the system doesn't have an API that does this in one call atomically, right? As unfortunate as performing a bit more work is, I think minimizing the potential security issues that typically come with TOC-TOU discrepancies is important here even if it means we need to do more work because we can't check for the optimal case first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmschonfeld Ok, I have updated this to just use the CreateDirectoryW result to determine if it already exists walking done the tree, then back up once it exists or is created.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmschonfeld Are we good with this now?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks this LGTM now (but would appreciate a double check on this from @compnerd before merging just to make sure everything else looks good)

@jmschonfeld jmschonfeld requested a review from jrflat August 28, 2025 21:04
@daveinglis daveinglis force-pushed the fix_long_window_paths branch from 9192a96 to e8e3ead Compare September 2, 2025 15:04
@daveinglis
Copy link
Contributor Author

@swift-ci please test

@daveinglis daveinglis force-pushed the fix_long_window_paths branch from e8e3ead to d9a17bb Compare September 2, 2025 15:11
@daveinglis
Copy link
Contributor Author

@swift-ci please test

var pwszCanonicalPath: LPWSTR?
let flags: ULONG = PATHCCH_ALLOW_LONG_PATHS
// Alway add the long path prefix since we don't know if this is a directory.
let flags: ULONG = PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH
Copy link
Contributor

@jakepetroules jakepetroules Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not also need the PATHCCH_ALLOW_LONG_PATHS flag anymore?

@daveinglis Also, the same code is copied to:

  • swift-system
  • swift-build
  • swift-subprocess

There are also variations in swift-tools-support-core and swift-testing.

Can you open PRs to make the change there as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PATHCCH_ALLOW_LONG_PATHS is not needed, PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH will always prepend the //?/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I saw the swift-build version, but it's never used in the context of creating a directory so should work fine as is. I can look at the other packages but if the usage is never used with creating directories do we still need it changed?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I saw the swift-build version, but it's never used in the context of creating a directory so should work fine as is. I can look at the other packages but if the usage is never used with creating directories do we still need it changed?

You never know when it might be used in new contexts, and because of cargo-culting I think we want to ensure that the most-correct implementation is kept in sync across all places it's copied to.

Ideally we'd have one function that everything could call into, but not a great place for this sort of SPI...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, I will start putting up some PR for the other version so that all match.

@daveinglis daveinglis force-pushed the fix_long_window_paths branch from d9a17bb to 3ec5ddc Compare September 4, 2025 20:28
@daveinglis
Copy link
Contributor Author

@swift-ci please test

return // Directory now exists, success
}
} else if lastError == ERROR_PATH_NOT_FOUND {
let parentPath = directoryPath.deletingLastPathComponent()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.deletingLastPathComponent() will replace all \ with / in the path, but I think it's OK since the next call to .withNTPathRepresentation will call GetFullPathNameW and normalize them back to \. I was curious if this might cause issues if someone passes a path that already has a \\?\ prefix to .createDirectory(atPath:), which would then have parent paths of the form //?/..., but I ran some simple tests and it still seems to work OK. We could add a .replacing(._slash, with: ._backslash) to the end to be safe.

The //?/ prefix needs to be added to directories if the length is
greater than MAX_PATH - 12, however the PATHCCH_ALLOW_LONG_PATHS to
PathAllocCanonicalize would only do that if the path was greater than
MAX_PATH. This change uses PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, which
forces it on all the time.  Just needed to remove the prefix in a few
places as it was making it way out into tests as this is suppose to be
transparent to the user.

This also fixes createDirectory() "withIntermediateDirectories: true"
for long paths on Windows.
@daveinglis daveinglis force-pushed the fix_long_window_paths branch from 3ec5ddc to 6b787ac Compare September 15, 2025 16:15
@daveinglis
Copy link
Contributor Author

@swift-ci please test

@daveinglis daveinglis merged commit aa97b79 into swiftlang:main Sep 15, 2025
19 checks passed
jrflat added a commit to jrflat/swift-foundation that referenced this pull request Sep 16, 2025
tshortli pushed a commit that referenced this pull request Sep 16, 2025
@tshortli
Copy link
Contributor

Reverted by #1512 because this broke the Windows build for Swift CI.

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

Successfully merging this pull request may close these issues.

createDirectory withIntermediateDirectories: true can fail with long paths
6 participants