Skip to content

Implement force deletion for RemoveAll#3236

Merged
peterebden merged 6 commits intothought-machine:masterfrom
peterebden:force-delete
Aug 27, 2024
Merged

Implement force deletion for RemoveAll#3236
peterebden merged 6 commits intothought-machine:masterfrom
peterebden:force-delete

Conversation

@peterebden
Copy link
Copy Markdown
Collaborator

@peterebden peterebden commented Aug 26, 2024

This replaces fs.ForceRemove with fs.RemoveAll and changes every call to os.RemoveAll to use it.
It attempts a standard delete, then if that fails attempts to chmod any non-writable directories it can find, then delete again.

This is IMO a bit cleaner than the previous implementation which was shelling out to rm, requiring a process executor (It's also less Unix-specific, which could be relevant in a far future if we ever tried to get Windows working). I wonder if that was one reason that only a small handful of cases called ForceRemove; anyway, it's not needed now.

Will fix / work around #3235 although I still fundamentally think Go should be doing something sensible there. I've bootstrapped a copy of Please on this machine (at least up to the Python stuff that I haven't finished fixing yet) with 1.23.0 and it seems fine.

@sean-
Copy link
Copy Markdown
Contributor

sean- commented Aug 26, 2024

Thank you!

Comment thread src/clean/clean.go Outdated
if async {
// Note that we can't fork() directly and continue running Go code, but ForkExec() works okay.
// Hence why we're using rm rather than fork() + os.RemoveAll.
// Hence why we're using rm rather than fork() + fs.RemoveAll.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I've noticed in a few of my worktrees (using noremote) that some of these non-writable files get left in plz-out (e.g. /home/slittley/core3/pukufmt/src/plz-out/tmp/third_party/go/_github.com_go-webauthn_x#dl._build/go_mod_download_folder/pkg/mod/golang.org/x/telemetry/config@v0.29.0/doc.go), which I think means that plz clean won't work correctly either.

We might want to change the non-async mode below to use fs.RemoveAll?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yup fair, it is a bit cleaner this way I think.

Comment thread src/fs/fs.go Outdated
// RemoveAll will try and remove the path with `os.RemoveAll`; if that fails with a permission error,
// it will attempt to adjust permissions to make things writable, then remove them.
func RemoveAll(path string) error {
if err := os.RemoveAll(path); err == nil || !errors.Is(err.(*os.PathError).Err, os.ErrPermission) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We've lost the os.IsNotExist check here (which should be errors.Is(err, fs.ErrNotExist)); is that ok?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yep, from the docs:

If the path does not exist, RemoveAll returns nil (no error)

I'm not sure why we were checking that before.

Comment thread src/fs/fs.go Outdated
// RemoveAll will try and remove the path with `os.RemoveAll`; if that fails with a permission error,
// it will attempt to adjust permissions to make things writable, then remove them.
func RemoveAll(path string) error {
if err := os.RemoveAll(path); err == nil || !errors.Is(err.(*os.PathError).Err, os.ErrPermission) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

err.(*os.PathError).Err looks like a potential panic to me. According to the godoc os.RemoveAll does always return a *PathError, but this conversion should be entirely unnecessary - *PathError implements Unwrap returning .Err, so errors.Is(err, os.ErrPermission) should work fine

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I tried that, it doesn't work.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Ok no fair enough, it does seem to be now. Not sure why I thought it wasn't originally - but agreed it is a heap nicer without the cast.

Comment thread src/fs/fs.go
return fmt.Errorf("failed to remove %s: %w\nOutput: %s", path, err, string(out))
}
return nil
return os.RemoveAll(path)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should also have a errors.Is(err, fs.ErrNotExist) check

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

as above, RemoveAll doesn't error if the path doesn't exist

@peterebden peterebden merged commit 1796f69 into thought-machine:master Aug 27, 2024
@peterebden peterebden deleted the force-delete branch August 27, 2024 15:47
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.

3 participants