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

cp: fix the result of inodes are not the same when preserve links is flagged #5064

Merged
merged 57 commits into from
Sep 24, 2023

Conversation

tommady
Copy link
Contributor

@tommady tommady commented Jul 10, 2023

fix #5031

@cakebaker cakebaker changed the title fix-issue-5031 cp: fix issue #5031 Jul 10, 2023
// FIXME: compare sources by the actual file they point to, not their path. (e.g. dir/file == dir/../dir/file in most cases)
show_warning!("source {} specified more than once", source.quote());
} else if preserve_hard_links && source.is_symlink() {
if let Some(new_source) = seen_sources.get(&source.read_link()?) {
Copy link
Contributor

Choose a reason for hiding this comment

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

please add a comment explaining why you do that
thanks

@github-actions
Copy link

GNU testsuite comparison:

GNU test failed: tests/cp/abuse. tests/cp/abuse is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/link-symlink. tests/cp/link-symlink is passing on 'main'. Maybe you have to rebase?
Skip an intermittent issue tests/tail-2/inotify-dir-recreate

@github-actions
Copy link

GNU testsuite comparison:

GNU test failed: tests/cp/abuse. tests/cp/abuse is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/link-symlink. tests/cp/link-symlink is passing on 'main'. Maybe you have to rebase?

@sylvestre
Copy link
Contributor

I guess you saw that your change regressed some tests

@github-actions
Copy link

GNU testsuite comparison:

GNU test failed: tests/cp/abuse. tests/cp/abuse is passing on 'main'. Maybe you have to rebase?
GNU test failed: tests/cp/link-symlink. tests/cp/link-symlink is passing on 'main'. Maybe you have to rebase?

@tommady
Copy link
Contributor Author

tommady commented Jul 12, 2023

I guess you saw that your change regressed some tests

Yap I saw
So that's why I keep this PR as draft

src/uu/cp/src/cp.rs Outdated Show resolved Hide resolved
@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/tail-2/inotify-dir-recreate

@tommady tommady marked this pull request as ready for review July 13, 2023 12:56
@tommady
Copy link
Contributor Author

tommady commented Jul 13, 2023

hi @sylvestre
I am unsure whether those two failed checks are related to this PR.

  1. Android one is failed on the boot
  2. fuzzers one is stuck on the compiling

and please review it.
thank you 🙏🏻

@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/tail-2/inotify-dir-recreate

@sylvestre
Copy link
Contributor

Fails on android, just disable the test on android:


running 1 test
touch: /data/data/com.termux/files/usr/tmp/.tmpcQS35T/a
symlink: /data/data/com.termux/files/usr/tmp/.tmpcQS35T/a,/data/data/com.termux/files/usr/tmp/.tmpcQS35T/b
symlink: /data/data/com.termux/files/usr/tmp/.tmpcQS35T/b,/data/data/com.termux/files/usr/tmp/.tmpcQS35T/c
mkdir: /data/data/com.termux/files/usr/tmp/.tmpcQS35T/d
run: /data/data/com.termux/files/home/coreutils/target/debug/coreutils cp --preserve=links -R -H b c d
test test_cp::test_cp_issue_5031_case_3 ... FAILED

failures:

failures:
    test_cp::test_cp_issue_5031_case_3

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 2634 filtered out; finished in 0.03s


--- TRY 3 STDERR:        coreutils::tests test_cp::test_cp_issue_5031_case_3 ---
thread 'test_cp::test_cp_issue_5031_case_3' panicked at 'Command was expected to succeed.
stdout = 
 stderr = cp: Permission denied (os error 13)
', tests/by-util/test_cp.rs:1438:10
stack backtrace:
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

@github-actions
Copy link

GNU testsuite comparison:

Skipping an intermittent issue tests/tail-2/inotify-dir-recreate

@sylvestre
Copy link
Contributor

test_cp_issue_5031_case_1 & test_cp_issue_5031_case_2 are failing too :)

@github-actions
Copy link

GNU testsuite comparison:

Skipping an intermittent issue tests/tail-2/inotify-dir-recreate

@tommady
Copy link
Contributor Author

tommady commented Aug 22, 2023

Hi @tertsdiepraam
I tried to address your comments, please help review while you have time, thank you 🙇🏻

about the

let found_hard_link = preserve_hardlinks(hard_links, &source_absolute, &dest)?;

I think we can do an enhancement in another PR.
WDYT?

@tertsdiepraam
Copy link
Member

tertsdiepraam commented Aug 26, 2023

Thanks for the fixes!

The reason that I want preserve_hardlinks to be removed is that (if I'm not mistaken) it's supposed to do the same thing as what you implemented. So your change should be to replace preserve_hardlinks rather than add something. That also means that we can't fully test your change now because it might be "helped" by the existing preserve_hardlinks. Does that make sense?

@tommady
Copy link
Contributor Author

tommady commented Aug 27, 2023

hmmm ya... that function is some kind of "protecting", "helping" me,
because that function serves a different purpose, although the name seems doing the same logic as this PR does.

I'll try what I can do to fulfill your and my goal.

@tertsdiepraam
Copy link
Member

Just to be clear: I'm not doubting your solution 😄 It looks good, but we shouldn't have two functions doing sort of the same thing. Thanks for taking a look!

Copy link
Member

@tertsdiepraam tertsdiepraam left a comment

Choose a reason for hiding this comment

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

Excellent! I think this is great now! I love that we're actually reducing the amount of code now (apart from the tests).

I have one more question about the part where you delete existing files, which I probably should have seen earlier, but I only noticed it now.

There are several follow up issues I'd like to open, but that we shouldn't look at now:

  • We should clean up the number of parameters we're passing around.
  • We should figure out what the issue with Android is 🤔
  • We're breaking redox support here. I don't think this is a problem, because we've probably already done that since we don't test Redox in the CI. We should probably get that going first and then start fixing the builds for Redox.

Comment on lines 1601 to 1616
// Consider the following files:
//
// * `src/f` - a regular file
// * `src/link` - a hard link to `src/f`
// * `dest/src/f` - a different regular file
//
// In this scenario, if we do `cp -a src/ dest/`, it is
// possible that the order of traversal causes `src/link`
// to get copied first (to `dest/src/link`). In that case,
// in order to make sure `dest/src/link` is a hard link to
// `dest/src/f` and `dest/src/f` has the contents of
// `src/f`, we delete the existing file to allow the hard
// linking.
if file_or_link_exists(dest) && file_or_link_exists(Path::new(new_source)) {
std::fs::remove_file(dest)?;
}
Copy link
Member

Choose a reason for hiding this comment

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

What if we move this down to below the handle_existing_dest call? Wouldn't that have the same behaviour? We should probably do the proper handling for existing destination files here too right?\

So we'd move the whole preserve hard links branch to below these lines:

if file_or_link_exists(dest) {
    handle_existing_dest(source, dest, options, source_in_command_line)?;
}

or wouldn't that work?

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 it will work due to this one (handle_existing_dest function) was protected by the preserve_hardlinks

Copy link
Member

Choose a reason for hiding this comment

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

What do you mean with by protected? Do you mean that it wasn't called for hard links before? It does do some very specific stuff so it might indeed not work for hard links...

Copy link
Contributor Author

@tommady tommady Aug 29, 2023

Choose a reason for hiding this comment

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

Sorry for the late response,
I mean I tried to do as you said so like below

    if file_or_link_exists(dest) {
        handle_existing_dest(source, dest, options, source_in_command_line)?;
    }

    if options.preserve_hard_links() {
        // if we encounter a matching device/inode pair in the source tree
        // we can arrange to create a hard link between the corresponding names
        // in the destination tree.
        if let Some(new_source) = copied_files.get(
            &FileInformation::from_path(source, options.dereference(source_in_command_line))
                .context(format!("cannot stat {}", source.quote()))?,
        ) {
            std::fs::hard_link(new_source, dest)?;
            return Ok(());
        };
    }

We could not do that
due to the handle_existing_dest function only deleting the destination file when the overwrite option was given.

so that function wasn't to act like it's named to deal with every scenario of existing destination As the comment of that function said depending on the options so ya... it does not solve the original problem (if I understand that function correctly)

Copy link
Member

Choose a reason for hiding this comment

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

But it should honor those options right? For example, if we give -n to (GNU) cp this happens:

❯ exa -Til
8732801 drwxr-xr-x - terts 29 aug 13:35 .
9036332 drwxr-xr-x - terts 29 aug 13:36 ├── dest
9034135 .rw-r--r-- 0 terts 29 aug 13:35 │  ├── f
9034136 .rw-r--r-- 0 terts 29 aug 13:37 │  └── g
9036331 drwxr-xr-x - terts 29 aug 13:34 └── src
9034134 .rw-r--r-- 0 terts 29 aug 13:34    ├── f
9034134 .rw-r--r-- 0 terts 29 aug 13:34    └── g

❯ cp -n --preserve=links src/f src/g dest
cp: not replacing 'dest/f'
cp: not replacing 'dest/g'

Note how src/f and src/g have the same inode so are hardlinks. Seems to me like the treatment of existing files is the same whether we are dealing with a second hard or not.

Copy link
Member

@tertsdiepraam tertsdiepraam Aug 29, 2023

Choose a reason for hiding this comment

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

Sorry, small correction. The actual case that shows a difference is:

❯ exa -Til                                                                                                                                                                                                  13:46:18
8732801 drwxr-xr-x - terts 29 aug 13:35 .
9036332 drwxr-xr-x - terts 29 aug 13:46 ├── dest
9034136 .rw-r--r-- 0 terts 29 aug 13:45 │  └── g
9036331 drwxr-xr-x - terts 29 aug 13:34 └── src
9034134 .rw-r--r-- 0 terts 29 aug 13:34    ├── f
9034134 .rw-r--r-- 0 terts 29 aug 13:34    └── g

where GNU does this:

❯ cp -n --preserve=links src/f src/g dest
cp: not replacing 'dest/g'

and this PR does this:

❯ cp -n --preserve=links src/f src/g dest

(No output, src/g is copied)

I think this would also be a nice test case to have in our test suite.

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, you are right.
let me make some modifications to fulfill that goal. 🙇🏻

@github-actions
Copy link

GNU testsuite comparison:

Congrats! The gnu test tests/mv/hard-3 is no longer failing!
GNU test failed: tests/cp/preserve-link. tests/cp/preserve-link is passing on 'main'. Maybe you have to rebase?

@github-actions
Copy link

GNU testsuite comparison:

Congrats! The gnu test tests/mv/hard-3 is no longer failing!
Skipping an intermittent issue tests/tail-2/inotify-dir-recreate

@github-actions
Copy link

GNU testsuite comparison:

Congrats! The gnu test tests/mv/hard-3 is no longer failing!
Skipping an intermittent issue tests/tail-2/inotify-dir-recreate

@tommady
Copy link
Contributor Author

tommady commented Aug 30, 2023

Hi @tertsdiepraam
I did some modifications to move the preserve links check after the handle existing dest function and add a new test case as you mentioned.

Please help review while you have time.

Thank you 🙇‍♂️

Copy link
Member

@tertsdiepraam tertsdiepraam left a comment

Choose a reason for hiding this comment

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

Thanks so much for sticking with this! Sorry it's taking so many iterations! Could you explain the change in handle_existing_dest? If we change the behaviour for all sorts of files, I'd like to see some commands for GNU cp that I can run to verify this behaviour?

Comment on lines 1509 to 1526
OverwriteMode::Clobber(ClobberMode::Standard) => {
// Consider the following files:
//
// * `src/f` - a regular file
// * `src/link` - a hard link to `src/f`
// * `dest/src/f` - a different regular file
//
// In this scenario, if we do `cp -a src/ dest/`, it is
// possible that the order of traversal causes `src/link`
// to get copied first (to `dest/src/link`). In that case,
// in order to make sure `dest/src/link` is a hard link to
// `dest/src/f` and `dest/src/f` has the contents of
// `src/f`, we delete the existing file to allow the hard
// linking.
if !source_in_command_line {
fs::remove_file(dest)?;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

This bit is not specific to hard links, right? So did we just miss it before? If it is only for hard links, should we make an if for it to check? Do you have test cases for this both for hard links and not hard links?

The source_in_command_line boolean also seems weird if that matters? Have you compared this behaviour with recursively copying directories with some hard links?

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 sorry I used the wrong condition, now I changed 🙇🏻

@github-actions
Copy link

GNU testsuite comparison:

Congrats! The gnu test tests/mv/hard-3 is no longer failing!

@github-actions
Copy link

github-actions bot commented Sep 6, 2023

GNU testsuite comparison:

Congrats! The gnu test tests/mv/hard-3 is no longer failing!

@sylvestre
Copy link
Contributor

Looks great. Do you know why GNU test: tests/cp/link-preserve.sh doesn't pass ?
fails later?

@github-actions
Copy link

GNU testsuite comparison:

Congrats! The gnu test tests/mv/hard-3 is no longer failing!

@tommady
Copy link
Contributor Author

tommady commented Sep 14, 2023

Looks great. Do you know why GNU test: tests/cp/link-preserve.sh doesn't pass ? fails later?

sorry for the late response, I re-run the test and everything seems fine.

@sylvestre sylvestre merged commit bd0fb81 into uutils:main Sep 24, 2023
45 checks passed
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.

cp --preserve=links -R -H doesn't keep the inodes
3 participants