-
Notifications
You must be signed in to change notification settings - Fork 303
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
65ed6aa
commit bbd80c1
Showing
4 changed files
with
130 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
//go:build !unix | ||
|
||
package job | ||
|
||
import "os" | ||
|
||
// hardRemoveAll only does more than os.RemoveAll on Unix-likes. | ||
func hardRemoveAll(path string) error { | ||
return os.RemoveAll(path) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
//go:build unix | ||
|
||
package job | ||
|
||
import ( | ||
"errors" | ||
"io/fs" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
) | ||
|
||
func TestHardRemoveAll(t *testing.T) { | ||
container, err := os.MkdirTemp("", "TestHardRemoveAll") | ||
if err != nil { | ||
t.Fatalf("os.MkdirTemp(TestHardRemoveAll) error = %v", err) | ||
} | ||
t.Cleanup(func() { os.RemoveAll(container) }) // lol but if hardRemoveAll doesn't work... | ||
|
||
dirA := filepath.Join(container, "a") | ||
dirB := filepath.Join(dirA, "b") | ||
fileC := filepath.Join(dirB, "c") | ||
if err := os.MkdirAll(dirB, 0o777); err != nil { | ||
t.Fatalf("os.MkdirAll(c%q, 0o777) = %v", dirB, err) | ||
} | ||
if err := os.WriteFile(fileC, []byte("hello!\n"), 0o664); err != nil { | ||
t.Fatalf("os.WriteFile(%q, hello!, 0o664) = %v", fileC, err) | ||
} | ||
|
||
// break directory perms | ||
if err := os.Chmod(dirB, 0o666); err != nil { | ||
t.Fatalf("os.Chmod(%q, 0o666) = %v", dirB, err) | ||
} | ||
if err := os.Chmod(dirA, 0o444); err != nil { | ||
t.Fatalf("os.Chmod(%q, 0o444) = %v", dirA, err) | ||
} | ||
|
||
if err := hardRemoveAll(dirA); err != nil { | ||
t.Errorf("hardRemoveAll(%q) = %v", dirA, err) | ||
} | ||
if _, err := os.Stat(dirA); !errors.Is(err, fs.ErrNotExist) { | ||
t.Errorf("os.Stat(%q) = %v, want %v", dirA, err, fs.ErrNotExist) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
//go:build unix | ||
|
||
package job | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
|
||
"golang.org/x/sys/unix" | ||
) | ||
|
||
// hardRemoveAll tries very hard to remove all items from the directory at path. | ||
// In addition to calling os.RemoveAll, it fixes missing +x bits on directories. | ||
func hardRemoveAll(path string) error { | ||
for { | ||
err := os.RemoveAll(path) | ||
if err == nil { // If os.RemoveAll worked, then exit early. | ||
return nil | ||
} | ||
// os.RemoveAll documents its only non-nil error as *os.PathError. | ||
pathErr, ok := err.(*os.PathError) | ||
if !ok { | ||
return err | ||
} | ||
|
||
// Did we not have permission to open something within a directory? | ||
if pathErr.Err != unix.EACCES { | ||
return err | ||
} | ||
dir := filepath.Dir(pathErr.Path) | ||
|
||
// Check that the EACCES was caused by mode on the directory. | ||
// (Note that this is a TOCTOU race, but we're not changing | ||
// owner uid/gid, and if something else is concurrently writing | ||
// files they can probably chmod +x their files themselves) | ||
di, statErr := os.Lstat(dir) | ||
if statErr != nil { | ||
return statErr | ||
} | ||
if !di.IsDir() { | ||
return err | ||
} | ||
if di.Mode()&0o3 == 0o3 || di.Mode()&0o030 == 0o30 || di.Mode()&0o300 == 0o300 { | ||
// Some other permission failure? | ||
return err | ||
} | ||
// Try to fix it with chmod +x dir | ||
if err := os.Chmod(dir, 0o777); err != nil { | ||
return err | ||
} | ||
// Now retry os.RemoveAll. | ||
} | ||
} |