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

fix: util/path: CheckSystemDriveAndRemoveDriveLetter to preserve / #5317

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

profnandaa
Copy link
Collaborator

@profnandaa profnandaa commented Sep 10, 2024

The call to CheckSystemDriveAndRemoveDriveLetter() does not preserve the trailing / or \\. This happens because filepath.Clean() strips away any trailing slashes. For example /sample/ will be \\sample on Windows. This function was mainly written for Windows scenarios, which have System Drive Letters like C:/, etc.

This was causing cases like COPY testfile /testdir/ to be intepreted as COPY testfile /testdir, and if testdir is not explictly created before the call, it ends up being treated as a destination file other than a directory.

Fix this by checking that if we have a trailing / or \\, we preserve it after the call to filepath.Clean().

Fixes #5249

PS.

Also fixed for cross-building Windows from Linux, that would fail silently:

Repro dockerfile without RUN:

FROM mcr.microsoft.com/windows/nanoserver:ltsc2022
COPY test1.txt /sample/
#RUN type \sample\test1.txt

Build log:

$ docker buildx build --platform windows/amd64 `
    --builder buildkitd-dev --no-cache --tag=windows-test . `
    --progress plain `
    --output type=local,dest=./output

#6 [2/2] COPY test1.txt /sample/
#6 DONE 0.1s

#7 exporting to client directory
#7 copying files 31B
#7 copying files 230.88MB 0.8s done
#7 DONE 0.8s

Checking results, sample is a file instead of a directory:

# before
$ ls -l output/sample
-rw-r--r-- 1 root root 6 Sep 24 08:57 output/sample

# after
$ ls -l output/sample
total 4
-rw-r--r-- 1 root root 6 Sep 24 08:57 test1.txt

NOTE: also covered cases like, where platform-specific filepath.Clean() won't strip out the \\ on Linux:

COPY test1.txt \\sample\\

@profnandaa profnandaa changed the title fix: util/path: CheckSystemDriveAndRemoveDriveLetter to preserve / fix: util/path: CheckSystemDriveAndRemoveDriveLetter to preserve / Sep 10, 2024
@profnandaa
Copy link
Collaborator Author

Looking into the CI failures...

@profnandaa
Copy link
Collaborator Author

Found the regression caused by this, thankfully coz of the checked in integration tests!

Dockerfile:

FROM mcr.microsoft.com/windows/nanoserver:ltsc2022
COPY test1.txt /sample/
RUN type \sample\test1.txt

COPY test1.txt /
COPY test1.txt /test2.txt

RUN type test1.txt
RUN type test2.txt

Build log:

Dockerfile:5
--------------------
   3 |     RUN type \sample\test1.txt
   4 |     
   5 | >>> COPY test1.txt /
   6 |     COPY test1.txt /test2.txt
   7 |     
--------------------
error: failed to solve: removing drive letter: UNC paths are not supported
Process 5812 has exited with status 1

Fixing.

@profnandaa profnandaa force-pushed the fix-5249-copy-dir-path branch 4 times, most recently from ac8552f to af77ee9 Compare September 11, 2024 08:00
util/system/path.go Outdated Show resolved Hide resolved
util/system/path.go Outdated Show resolved Hide resolved
util/system/path.go Outdated Show resolved Hide resolved
util/system/path.go Outdated Show resolved Hide resolved
@profnandaa profnandaa force-pushed the fix-5249-copy-dir-path branch 3 times, most recently from 36e398e to 1a5289c Compare September 13, 2024 08:21
@profnandaa profnandaa marked this pull request as draft September 13, 2024 08:51
@profnandaa profnandaa marked this pull request as ready for review September 17, 2024 17:25
@profnandaa profnandaa force-pushed the fix-5249-copy-dir-path branch 2 times, most recently from aca0ccb to e8ae74c Compare September 24, 2024 08:27
@profnandaa profnandaa force-pushed the fix-5249-copy-dir-path branch 5 times, most recently from 5dd50bb to 2d75bcd Compare October 9, 2024 04:31
util/system/path.go Outdated Show resolved Hide resolved
@gabriel-samfira
Copy link
Collaborator

LGTM. Waiting for a second review before merging.

@profnandaa
Copy link
Collaborator Author

profnandaa commented Oct 9, 2024 via email

@profnandaa
Copy link
Collaborator Author

@crazy-max @tonistiigi -- can take a look at this one?

Copy link
Member

@tonistiigi tonistiigi left a comment

Choose a reason for hiding this comment

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

Still don't understand this in relation to #5317 (comment) comment.

We have a NormalizePath function that takes keepSlash parameter. Afaics that implementation is still linux-specific and does not understand windows inputOS with backslashes.

This adds a new function cleanPath() that doesn't take keepSlash but automatically adds / is input path contained it. That is not the behavior of path.Clean so minimally function should be called cleanPathKeepSlash(), but ideally we should keep the functionality to keep slash into a single function and not multiple, and NormalizePath should not be linux-specific.

util/system/path.go Outdated Show resolved Hide resolved
@profnandaa
Copy link
Collaborator Author

profnandaa commented Oct 16, 2024

Hi @tonistiigi --

Still don't understand this in relation to #5317 (comment) comment.

We have a NormalizePath function that takes keepSlash parameter. Afaics that implementation is still linux-specific and does not understand windows inputOS with backslashes.

The problem is that we don't get a chance to get to NormalizePath, the trailing slash is stripped away at CheckSystemDriveAndRemoveDriveLetter. So, there's nothing to keep by that time:

func pathRelativeToWorkingDir(s llb.State, p string, platform ocispecs.Platform) (string, error) {
dir, err := s.GetDir(context.TODO(), llb.Platform(platform))
if err != nil {
return "", err
}
p, err = system.CheckSystemDriveAndRemoveDriveLetter(p, platform.OS)
if err != nil {
return "", errors.Wrap(err, "removing drive letter")
}
if system.IsAbs(p, platform.OS) {
return system.NormalizePath("/", p, platform.OS, true)
}
// add slashes for "" and "." paths
// "" is treated as current directory and not necessariy root
if p == "." || p == "" {
p = "./"
}
return system.NormalizePath(dir, p, platform.OS, true)
}

This adds a new function cleanPath() that doesn't take keepSlash but automatically adds / is input path contained it. That is not the behavior of path.Clean so minimally function should be called cleanPathKeepSlash(), but ideally we should keep the functionality to keep slash into a single function and not multiple, and NormalizePath should not be linux-specific.

I'm okay with renaming this for clarity. On the second part, the only problem is that we don't get to the NormalizePath function..
Another idea I'd toiled with is to have add a keepSlash param to CheckSystemDriveAndRemoveDriveLetter; but since this is already an exported function, was going to break the backward compatibility.

The more simplistic option I had is just adding back the trailing slash before we call into NormalizePath on L1821; but felt that will be too patchy...

Your thoughts?

@tonistiigi
Copy link
Member

but since this is already an exported function

Don't worry about Go backwards compatibility. We only care about API backwards compatibility.

The problem is that we don't get a chance to get to NormalizePath, #5249 (comment). So, there's nothing to keep by that time:

NormalizePath also calls into CheckSystemDriveAndRemoveDriveLetter (2x actually) so it looks like there might be some duplication in the codepath. Better to make it clear which functions responsibility it is to keep the slash and make sure this implementation is cross-platform capable.

@profnandaa
Copy link
Collaborator Author

but since this is already an exported function

Don't worry about Go backwards compatibility. We only care about API backwards compatibility.

Thanks for this, we can then have keepSlash (preferably, be declared once and) run through both functions for consistency.

The problem is that we don't get a chance to get to NormalizePath, #5249 (comment). So, there's nothing to keep by that time:

NormalizePath also calls into CheckSystemDriveAndRemoveDriveLetter (2x actually) so it looks like there might be some duplication in the codepath. Better to make it clear which functions responsibility it is to keep the slash and make sure this implementation is cross-platform capable.

I also noticed that multiple runs through CheckSystemDriveAndRemoveDriveLetter; I can work on some optimizations as a follow up.

Copy link
Collaborator Author

@profnandaa profnandaa left a comment

Choose a reason for hiding this comment

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

PTAL at the new changes I've made from the review recommendations. @gabriel-samfira @tonistiigi

util/system/path.go Outdated Show resolved Hide resolved
solver/llbsolver/file/backend.go Show resolved Hide resolved
@profnandaa
Copy link
Collaborator Author

Will investigate and fix the CI failures.

@profnandaa profnandaa force-pushed the fix-5249-copy-dir-path branch 6 times, most recently from be12cf3 to b8d27d1 Compare October 23, 2024 04:16
@profnandaa
Copy link
Collaborator Author

Will investigate and fix the CI failures.

CI failures now fixed, PTAL @gabriel-samfira @tonistiigi

solver/llbsolver/file/backend.go Outdated Show resolved Hide resolved
util/system/path.go Outdated Show resolved Hide resolved
// Returns path with platform specific separators.
// See https://github.com/moby/buildkit/issues/5249
func cleanPath(origPath string) string {
cleanedPath := filepath.Clean(origPath)
Copy link
Member

Choose a reason for hiding this comment

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

I guess this has not been resolved?

frontend/dockerfile/dockerfile2llb/convert.go Outdated Show resolved Hide resolved
The call to CheckSystemDriveAndRemoveDriveLetter() does not preserve
the trailing `/` or `\\`. This happens because `filepath.Clean()`
strips away any trailing slashes. For example `/sample/` will be
`\\sample` on Windows and `/sample` on Linux.
This function was mainly written for Windows scenarios, which
have System Drive Letters like C:/, etc.

This was causing cases like `COPY testfile /testdir/` to
be intepreted as `COPY testfile /testdir`, and if `testdir` is
not explictly created before the call, it ends up being treated
as a destination file other than a directory.

Fix this by checking that if we have a trailing `/` or `\\`, we
preserve it after the call to `filepath.Clean()`.

Also refactor `CheckSystemDriveAndRemoveDriveLetter` function to take
an extra keepSlash bool param, to be consistent with what is passed
to `NormalizePath`.

The rest of the calls to this function has left keepSlash = false
as the default behavior.

Fixes moby#5249

PS. Also fixed for cross-building from Linux scenario, taking care
for paths like `\\sample\\` that are not changed when run
through `filepath.Clean()`.

Signed-off-by: Anthony Nandaa <profnandaa@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

WCOW: trailing / ignored during COPY if destination dir not present
3 participants