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

Rewrite cp #3323

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

Rewrite cp #3323

wants to merge 2 commits into from

Conversation

apostasie
Copy link
Contributor

@apostasie apostasie commented Aug 17, 2024

The motivation for this is to address all issues (and variants not listed as individual tickets) in:

The fundamental problem with our current implementation of cp is that the logic to figure out if a destination is read-only is incomplete.
One of the key reason for it is that we do not follow symlinks, or relative paths, inside a container destination. For example, if we are copying to a path located into a readonly volume, we stop there, while the path may very well resolve to a completely different volume (or the rootfs).
Furthermore, there are other logic issues involving readonly rootfs, leading to situations where we DO copy while we should not, and conversely, where we do NOT copy while we should.

There is a also a fair amount of duplication, and finally, test isolation was problematic.

For this PR, the highlights are, for the testing part:

  • tests rewritten from scratch to cleanly separate test cases and test rig (should make adding more tests cases easier)
  • expanded test-suite to add more cases
  • better isolation
  • beside the "regular" expanded tests, added a "TestAcidCopy" test meant to reproduce corner/complicated cases and other "special" conditions that do not fit in the normal test rig

For the code part:

  • provide a mechanism to fully resolve a container path while on the host when all we have is the mounted root snapshot
  • provide a range of more expressive and informative errors for the user to diagnose issues
  • should address all link resolution, relative paths and read-only concerns

Also note this is superseding #3275

I appreciate this is a somewhat sizable PR (although a very large part of it (about 1500 lines) are just for tests).

If there is a different (simpler) approach to this problem - that would pass the test-suite - I am happy to re-evaluate of course.

@apostasie apostasie force-pushed the b-dev-cp branch 4 times, most recently from f1662be to a486b0a Compare August 17, 2024 22:48
options.SrcPath,
options.GOptions.Snapshotter,
options.FollowSymLink)
options)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Simplifying signature

pkg/testutil/testutil.go Outdated Show resolved Hide resolved
@apostasie apostasie force-pushed the b-dev-cp branch 8 times, most recently from 5ff18ff to 7d17a10 Compare August 21, 2024 20:06
err = errors.Join(errors.New("unable to retrieve containers with error"), err)
} else {
err = fmt.Errorf("no container found for: %s", options.ContainerReq)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Make sure we report the right error message to the user.

if err := os.MkdirAll(dstFull, 0o755); err != nil {
return err

// XXX FIXME: this seems wrong. What about ownership? We could be doing that inside a container
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not touching this right now.
This is previous code.
Am I mis-reading this, or is this a problem when doing that in a container target? (eg: shouldn't we do that as part of the nsenter/re-exec routine?)

resp, err := client.SnapshotService(snapshotter).Mounts(ctx, snapKey)
if err != nil {
return "", nil, err
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I know you don't like these Akihiro, but would you reconsider? It does make code much easier to read for these old eyes here :-)

@@ -0,0 +1,436 @@
/*
Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is the gist of it, that provides (proper) symlink and path resolution.

@@ -0,0 +1,24 @@
/*
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If one day we support this on Windows - we will need the win version of volumeNameLen

// FIXME: the following will break the test (anything that will evaluate on the shell, obviously):
// - `
// - $a, ${a}, etc
complexify = "" // = "-~a0-_.(){}[]*#! \"'∞"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Useful to stress test rig when modifying it. Deactivated by default as it makes things much harder to read.

@apostasie apostasie changed the title [WIP] Rewrite cp Rewrite cp Aug 21, 2024
@apostasie apostasie marked this pull request as ready for review August 21, 2024 21:06
@apostasie
Copy link
Contributor Author

CI is green.

PTAL at your convenience.

@AkihiroSuda AkihiroSuda requested a review from a team August 23, 2024 07:18
@apostasie
Copy link
Contributor Author

Rebased.
Pending CI.

@apostasie
Copy link
Contributor Author

apostasie commented Sep 3, 2024

Obviously, the move to sub-packages is further exposing problems in our tests (chiefly because the parallel pool is greatly reduced - split in 15 buckets - which significantly increase the risk of a negative interaction, and the chance of cross package leftovers (15x)).

The introduction of a lot more / longer cp tests with this PR is likely tripping unrelated tests - and there seem to be something wrong with encrypt tests (starting with the fact that they are pruning), and even for the docker compat suite (with prune again).

That doesn't change things for this PR fundamentally, as the problem is not the code in here (which is very limited in its impact) - but obviously I'll send a couple of other PRs first to fix some of our tests issues so this here gets green.

@apostasie
Copy link
Contributor Author

Pending #3402

@apostasie apostasie marked this pull request as draft September 4, 2024 17:15
@apostasie
Copy link
Contributor Author

Reverting to draft until I figure out what's going on with testing.

@apostasie
Copy link
Contributor Author

Will wait for new tooling framework to land to update tests and get some clarity on what's going on.

@apostasie
Copy link
Contributor Author

As soon as #3492 is in, will rewrite tests with the new framework and get this in shape.

@apostasie apostasie force-pushed the b-dev-cp branch 2 times, most recently from a5aab67 to f10b13a Compare October 18, 2024 23:12
@apostasie apostasie force-pushed the b-dev-cp branch 3 times, most recently from ecf3af5 to 5c04821 Compare December 4, 2024 07:30
@apostasie
Copy link
Contributor Author

@djdongjin I paused this (and other feature / refactor work) 3 months ago because the CI failed randomly, as testing became unmanageable at that point, and I embarked on the new test framework and tests clean-up overall instead.

Rebasing this on top of all of that then, CI is now green.

I will do another final proof-read of this PR before turning it to "ready for review" - but otherwise, I am still interested in getting this in as it will fix a number of issues with cp, so, would love your updated thoughts on this if/when you have bandwidth for that.

@@ -115,16 +115,16 @@ func processCpOptions(cmd *cobra.Command, args []string) (types.ContainerCpOptio
}

container2host := srcSpec.Container != nil
var container string
var containerReq string
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do not shadow package name.

@apostasie apostasie marked this pull request as ready for review December 4, 2024 22:02
@AkihiroSuda AkihiroSuda added this to the v2.0.2 milestone Dec 5, 2024
pkg/cmd/container/cp_linux.go Outdated Show resolved Hide resolved
err = errors.Join(errors.New("unable to copy"), err)
} else if count == 0 {
if err != nil {
err = errors.Join(errors.New("unable to retrieve containers with error"), err)
Copy link
Member

Choose a reason for hiding this comment

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

same behavior you should panic if err != nil with count == 0

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reading into this now. Thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think we should panic here.

pkg/containerutil/cp_resolve_linux.go Show resolved Hide resolved
if err != nil {
return nil, errors.Join(errCannotResolvePathNoCwd, err)
}
path = cwd + string(os.PathSeparator) + path
Copy link
Member

Choose a reason for hiding this comment

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

filepath.Join(cwd, path)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I do not fully remember the detailed reason of why in that case, but I am pretty sure it would not work. filepath.Join will clean the path, which we do not want in that case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Re-reading. We absolutely do not want filepath.Join here. That would defeat the whole purpose of resolving the final path properly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

filepath.Join will "clean" the path (eg: reduce relative path). The whole problem and point here is to perform proper resolution instead.

Copy link
Member

Choose a reason for hiding this comment

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

IMO, we should reflect this intention in a comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is a common misunderstanding about lexical functions - they should not be used before resolving symlinks.

isn't safer to resolve the symlink before and then work with cleaned path ?

I am not completely sure what you mean - but here, the path may be relative, so in that case we do need to start resolving against cwd.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see that you evaluate the symlink at this line https://github.com/containerd/nerdctl/pull/3323/files#diff-f8d3022dbe81c88660be3b5c26107695a00ef30d103f67558a218780e5da1dbaR81 so may be resolving the relative path just after it ?

We need to account for the fact that the path may not exist - which is handled a few lines below.
Again not completely sure what you mean here, but see previous comment ^ - we need to handle relative path cases (which is the reason we resolve links against cwd + path).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@fahedouch @djdongjin I wish this could be simpler.

Fact is, handling path resolution properly for files that may not exist (yet) is not a problem as simple as "let's lexical path join".

Case in point: docker config path resolution is hosed as well #3413 - and in their case, it is a much simpler problem.

There is some (moderately) useful reading over here, on the topic: https://9p.io/sys/doc/lexnames.html

So, point is: I am not interested in forcing anything here.
We all have better things to do.
If you folks are not comfortable with this PR, I would just accept that and close it.
This is fine. Not everything is meant to end-up accepted.

Anyhow, let me know your thoughts.

Copy link
Member

Choose a reason for hiding this comment

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

Hi @apostasie, I will take another look when I get some time.

So, point is: I am not interested in forcing anything here.
We all have better things to do.
If you folks are not comfortable with this PR, I would just accept that and close it.

I don't think folks imply you're forcing anything or not comfortable with your change. I think mostly it's because people (including me) may be slow at responding recently due to holidays approaching.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

All fine @djdongjin
I want(ed) to make sure that people feel comfortable with a “no”
I am a simple and straightforward person 😁

Enjoy the holiday folks!

Signed-off-by: apostasie <[email protected]>
Signed-off-by: apostasie <[email protected]>
@apostasie
Copy link
Contributor Author

apostasie commented Dec 12, 2024

Rebased.

Comments answered or addressed with latest push.

Pending green CI. CI is green

@apostasie apostasie requested a review from fahedouch December 12, 2024 03:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants