Skip to content

Fix FileUtils.rm_rf to only mask exceptions about non existent files#58

Closed
deivid-rodriguez wants to merge 2 commits intoruby:masterfrom
deivid-rodriguez:let_rm_rf_fail_when_it_doesnt_remove_the_folder
Closed

Fix FileUtils.rm_rf to only mask exceptions about non existent files#58
deivid-rodriguez wants to merge 2 commits intoruby:masterfrom
deivid-rodriguez:let_rm_rf_fail_when_it_doesnt_remove_the_folder

Conversation

@deivid-rodriguez
Copy link
Contributor

This is one idea to fix #57.

I think this is a bug, so I didn't care about backwards compatibility. In my opinion, any code forcefully removing a folder expects the folder to be removed, and if not, it will fail later on, like it happened in bundler.

@deivid-rodriguez
Copy link
Contributor Author

Apparently my changes broke the JRuby CI, I'll have a look.

@deivid-rodriguez
Copy link
Contributor Author

I'm thinking maybe an alternative fix would be to try give the folder executable permissions before trying to remove it?

@deivid-rodriguez deivid-rodriguez force-pushed the let_rm_rf_fail_when_it_doesnt_remove_the_folder branch from baedd10 to dd8b954 Compare September 2, 2021 12:36
@deivid-rodriguez deivid-rodriguez changed the title Let FileUtils.rm_rf fail when it doesn't remove the folder Fix FileUtils.rm_rf when target folder is not empty and has no executable permission set Sep 2, 2021
@deivid-rodriguez deivid-rodriguez force-pushed the let_rm_rf_fail_when_it_doesnt_remove_the_folder branch from dd8b954 to 2860e88 Compare September 2, 2021 12:58
@deivid-rodriguez
Copy link
Contributor Author

Alright, I changed the approach to automatically try fixing permissions in this particular case, because:

  • It should be 100% backwards compatible.
  • It's an approach already used for other edge cases.

@deivid-rodriguez
Copy link
Contributor Author

For JRuby I had to add some special code, because it raises a different error from CRuby. Issue is already reported upstream: jruby/jruby#5617.

@deivid-rodriguez deivid-rodriguez force-pushed the let_rm_rf_fail_when_it_doesnt_remove_the_folder branch 3 times, most recently from 74d957d to e53e336 Compare October 7, 2021 19:35
@deivid-rodriguez deivid-rodriguez force-pushed the let_rm_rf_fail_when_it_doesnt_remove_the_folder branch from e53e336 to 5649416 Compare October 11, 2021 17:33
@deivid-rodriguez
Copy link
Contributor Author

Alright, I changed my mind again on this.

I think fileutils should just be a thin wrapper on top of standard shell utils, so FileUtils.rm_rf should behave just like rm -rf and not try to fix permissions at all.

The bug reported still stands, and I think my original approach would be best, so I'll go back to that.

@deivid-rodriguez deivid-rodriguez marked this pull request as draft October 13, 2021 20:15
@hsbt hsbt mentioned this pull request Oct 21, 2021
@deivid-rodriguez deivid-rodriguez force-pushed the let_rm_rf_fail_when_it_doesnt_remove_the_folder branch 3 times, most recently from ff13121 to c702105 Compare March 13, 2022 20:16
@deivid-rodriguez deivid-rodriguez changed the title Fix FileUtils.rm_rf when target folder is not empty and has no executable permission set Fix FileUtils.rm_rf to only mask exceptions about non existent files Mar 13, 2022
@deivid-rodriguez deivid-rodriguez marked this pull request as ready for review March 13, 2022 20:26
@deivid-rodriguez
Copy link
Contributor Author

I reencountered an issue related to this.

I was debugging a dependabot spec failure on Bundler 1.x + Ruby 3.1, and it was very hard to debug because the underlying issue ended up being a FileUtils.rm_rf call that was failing silently due to the vendored version of fileutils provided by Bundler 1.x not supporting the separation of keyword and positional arguments in Ruby 3.

So a

ArgumentError: wrong number of arguments (given 2, expected 1)

was being raised, but then swallowed, and the error was happening later on on a completely unrelated place, due to the spec assuming removal had happened successfully.

So, I changed this PR to only mask the errors about non existent files when the force flag is given.

This matches exactly rm shell behavior and man rm documentation, which states

COMPATIBILITY

The rm utility differs from historical implementations in that the -f option only masks attempts to remove non-existent files instead of masking a large variety of errors.

@deivid-rodriguez
Copy link
Contributor Author

Hello @nobu @hsbt, I'd like some feedback about this if possible. Thank you 🙏.

Luckily the bad permissions were not having any effect in the test
because empty directories can always be removed, but 700 is a decimal
number which is 1274 in octal, which means no executable permissions for
the user, which is unintended here.
@deivid-rodriguez deivid-rodriguez force-pushed the let_rm_rf_fail_when_it_doesnt_remove_the_folder branch from c702105 to 2f0a8c3 Compare April 16, 2022 10:34
@deivid-rodriguez deivid-rodriguez force-pushed the let_rm_rf_fail_when_it_doesnt_remove_the_folder branch from 2f0a8c3 to 27a88c0 Compare April 16, 2022 11:09
@deivid-rodriguez
Copy link
Contributor Author

Sorry for the ping, this patch still has some issues. I'll ping you back once it's ready!

To behave like the standard `rm` command, it should only ignore
exceptions about not existing files, not every exception. This should
make debugging some errors easier, because the expectation is that `rm
-rf` will succeed if and only if, all given files (previously existent
or not) are removed. However, due to this exception swallowing, this is
not always the case.

From the `rm` man page

> COMPATIBILITY
>
> The rm utility differs from historical implementations in that the -f
> option only masks attempts to remove non-existent files instead of
> masking a large variety of errors.
@deivid-rodriguez deivid-rodriguez force-pushed the let_rm_rf_fail_when_it_doesnt_remove_the_folder branch from 27a88c0 to 1fb73bf Compare April 29, 2022 16:14
@deivid-rodriguez
Copy link
Contributor Author

Ok, so I run this code with RubyGems tests and it detected a couple of issues in the tests. See ruby/rubygems#5475. I also run this changeset against ruby/ruby CI at ruby/ruby#5811 and everything is green after fixing Rubygems test issues.

So, this change could break some tests, but in my opinion, it will be for good because it will help detect issues. Given that, I see a couple of options:

  • Ship this as is and call it a bug fix.
  • Ship this as fileutils 2.0.0.
  • Make this really backwards compatible by adding a separate value for this behavior, for example, force = :strict or something.

I haven't get any feedback at all so far, so I will wait a few days and then bring it to ruby-core.

@eregon
Copy link
Member

eregon commented May 16, 2022

Looks good to me, indeed swallowing exceptions in gems/libraries leads to nasty hard-to-debug bugs and I think hurts more than help.

@deivid-rodriguez
Copy link
Contributor Author

I found a better alternative to be able to see the original exception that does not require any changes in fileutils: using FileUtils.remove_entry(path) if File.exist?(path). That's equivalent to FileUtils.rm_rf(path) but without the exception swallowing I'm reporting here.

So I'm closing this PR given it has been ignored for a long time.

@deivid-rodriguez deivid-rodriguez deleted the let_rm_rf_fail_when_it_doesnt_remove_the_folder branch May 24, 2022 12:02
end
end
end
ensure
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 realized this ensure might have been intentional.

The current behavior is, even if an exception happen, still try to remove the given folder (and most likely get a Errno::ENOTEMPTY exception, because the original exception probably prevented some of the files inside to be removed). In that case, the original exception is still reported as the exception cause.

In Bundler, I was not seeing the original error because we're failing to report the cause if it exists in a couple of places, but I will fix this in Bundler.

This is all to say that, if the behavior I proposed is to be implemented, we might still want to restore this ensure.

@eregon
Copy link
Member

eregon commented May 24, 2022

One issue with FileUtils.remove_entry is it's not really an intuitive name, and probably far more people will run into the shortcomings of FileUtils.rm_rf(path).

@deivid-rodriguez
Copy link
Contributor Author

Yeah, when I said "better alternative" I meant to what I'm doing now (rechecking whether the folder still exists after FileUtils.rm_rf, and raise an exception "guessing" the actual culprit). But fixing FileUtils.rm_rf would be best in my opinion.

@codiophile
Copy link

I do agree that rm_rf is the most intuitive name and the method that most people will be using. I also believe that most people will assume that rm_rf will behave like rm -rf, which will cause issues further down the line, because rm_rf is silently failing, causing issues elsewhere.

It seems like it would be more useful, if it ignored does not exist errors (and warned about ~ not working, which is what got me), and threw other errors, as most people would expect that the thing they are trying to delete will be gone after it's been deleted.

I wonder why this issue has went ignored, as it seems like a high value thing to fix. Sure :force ignoring all errors is a feature, but it seems like a feature that does more harm than good. If you really want that feature, you can just swallow all errors yourself.

@deivid-rodriguez
Copy link
Contributor Author

It didn't go ignored, it just took a lot to discuss it because people are busy. But as I said somewhere else, it actually got some traction, see #99.

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.

FileUtils.rm_rf doesn't raise when directory could not be removed

3 participants