mirror of
https://github.com/golang/go
synced 2024-11-27 04:11:22 -07:00
os: add support for long path names on unix RemoveAll
On unix systems, long enough path names will fail when performing syscalls
like `Lstat`. The current RemoveAll uses several of these syscalls, and so
will fail for long paths. This can be risky, as it can let users "hide"
files from the system or otherwise make long enough paths for programs
to fail. By using `Unlinkat` and `Openat` syscalls instead, RemoveAll is
safer on unix systems. Initially implemented for linux, darwin, and several bsds.
Fixes #27029
Co-authored-by: Giuseppe Capizzi <gcapizzi@pivotal.io>
Co-authored-by: Julia Nedialkova <yulia.nedyalkova@sap.com>
Change-Id: Id9fcdf4775962b021b7ff438dc51ee6d16bb5f56
GitHub-Last-Rev: b30a621fe3
GitHub-Pull-Request: golang/go#27871
Reviewed-on: https://go-review.googlesource.com/c/137442
Run-TryBot: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
parent
7bada2cf46
commit
85143d3554
58
src/internal/syscall/unix/at.go
Normal file
58
src/internal/syscall/unix/at.go
Normal file
@ -0,0 +1,58 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux darwin freebsd openbsd netbsd dragonfly
|
||||
|
||||
package unix
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func Unlinkat(dirfd int, path string, flags int) error {
|
||||
var p *byte
|
||||
p, err := syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, errno := syscall.Syscall(unlinkatTrap, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags))
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Openat(dirfd int, path string, flags int, perm uint32) (int, error) {
|
||||
var p *byte
|
||||
p, err := syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
fd, _, errno := syscall.Syscall6(openatTrap, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags), uintptr(perm), 0, 0)
|
||||
if errno != 0 {
|
||||
return 0, errno
|
||||
}
|
||||
|
||||
return int(fd), nil
|
||||
}
|
||||
|
||||
func Fstatat(dirfd int, path string, stat *syscall.Stat_t, flags int) error {
|
||||
var p *byte
|
||||
p, err := syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, errno := syscall.Syscall6(fstatatTrap, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(stat)), uintptr(flags), 0, 0)
|
||||
if errno != 0 {
|
||||
return errno
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
12
src/internal/syscall/unix/at_sysnum_darwin.go
Normal file
12
src/internal/syscall/unix/at_sysnum_darwin.go
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package unix
|
||||
|
||||
const unlinkatTrap = uintptr(472)
|
||||
const openatTrap = uintptr(463)
|
||||
const fstatatTrap = uintptr(469)
|
||||
|
||||
const AT_REMOVEDIR = 0x80
|
||||
const AT_SYMLINK_NOFOLLOW = 0x0020
|
14
src/internal/syscall/unix/at_sysnum_dragonfly.go
Normal file
14
src/internal/syscall/unix/at_sysnum_dragonfly.go
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package unix
|
||||
|
||||
import "syscall"
|
||||
|
||||
const unlinkatTrap uintptr = syscall.SYS_UNLINKAT
|
||||
const openatTrap uintptr = syscall.SYS_OPENAT
|
||||
const fstatatTrap uintptr = syscall.SYS_FSTATAT
|
||||
|
||||
const AT_REMOVEDIR = 0x2
|
||||
const AT_SYMLINK_NOFOLLOW = 0x1
|
14
src/internal/syscall/unix/at_sysnum_freebsd.go
Normal file
14
src/internal/syscall/unix/at_sysnum_freebsd.go
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package unix
|
||||
|
||||
import "syscall"
|
||||
|
||||
const unlinkatTrap uintptr = syscall.SYS_UNLINKAT
|
||||
const openatTrap uintptr = syscall.SYS_OPENAT
|
||||
const fstatatTrap uintptr = syscall.SYS_FSTATAT
|
||||
|
||||
const AT_REMOVEDIR = 0x800
|
||||
const AT_SYMLINK_NOFOLLOW = 0x200
|
11
src/internal/syscall/unix/at_sysnum_fstatat64_linux.go
Normal file
11
src/internal/syscall/unix/at_sysnum_fstatat64_linux.go
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build arm mips mipsle 386
|
||||
|
||||
package unix
|
||||
|
||||
import "syscall"
|
||||
|
||||
const fstatatTrap uintptr = syscall.SYS_FSTATAT64
|
11
src/internal/syscall/unix/at_sysnum_fstatat_linux.go
Normal file
11
src/internal/syscall/unix/at_sysnum_fstatat_linux.go
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build arm64
|
||||
|
||||
package unix
|
||||
|
||||
import "syscall"
|
||||
|
||||
const fstatatTrap uintptr = syscall.SYS_FSTATAT
|
13
src/internal/syscall/unix/at_sysnum_linux.go
Normal file
13
src/internal/syscall/unix/at_sysnum_linux.go
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package unix
|
||||
|
||||
import "syscall"
|
||||
|
||||
const unlinkatTrap uintptr = syscall.SYS_UNLINKAT
|
||||
const openatTrap uintptr = syscall.SYS_OPENAT
|
||||
|
||||
const AT_REMOVEDIR = 0x200
|
||||
const AT_SYMLINK_NOFOLLOW = 0x100
|
14
src/internal/syscall/unix/at_sysnum_netbsd.go
Normal file
14
src/internal/syscall/unix/at_sysnum_netbsd.go
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package unix
|
||||
|
||||
import "syscall"
|
||||
|
||||
const unlinkatTrap uintptr = syscall.SYS_UNLINKAT
|
||||
const openatTrap uintptr = syscall.SYS_OPENAT
|
||||
const fstatatTrap uintptr = syscall.SYS_FSTATAT
|
||||
|
||||
const AT_REMOVEDIR = 0x800
|
||||
const AT_SYMLINK_NOFOLLOW = 0x200
|
11
src/internal/syscall/unix/at_sysnum_newfstatat_linux.go
Normal file
11
src/internal/syscall/unix/at_sysnum_newfstatat_linux.go
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build amd64 mips64 mips64le ppc64 ppc64le s390x
|
||||
|
||||
package unix
|
||||
|
||||
import "syscall"
|
||||
|
||||
const fstatatTrap uintptr = syscall.SYS_NEWFSTATAT
|
14
src/internal/syscall/unix/at_sysnum_openbsd.go
Normal file
14
src/internal/syscall/unix/at_sysnum_openbsd.go
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package unix
|
||||
|
||||
import "syscall"
|
||||
|
||||
const unlinkatTrap uintptr = syscall.SYS_UNLINKAT
|
||||
const openatTrap uintptr = syscall.SYS_OPENAT
|
||||
const fstatatTrap uintptr = syscall.SYS_FSTATAT
|
||||
|
||||
const AT_REMOVEDIR = 0x08
|
||||
const AT_SYMLINK_NOFOLLOW = 0x02
|
@ -5,7 +5,6 @@
|
||||
package os
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
@ -58,101 +57,3 @@ func MkdirAll(path string, perm FileMode) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveAll removes path and any children it contains.
|
||||
// It removes everything it can but returns the first error
|
||||
// it encounters. If the path does not exist, RemoveAll
|
||||
// returns nil (no error).
|
||||
func RemoveAll(path string) error {
|
||||
// Simple case: if Remove works, we're done.
|
||||
err := Remove(path)
|
||||
if err == nil || IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Otherwise, is this a directory we need to recurse into?
|
||||
dir, serr := Lstat(path)
|
||||
if serr != nil {
|
||||
if serr, ok := serr.(*PathError); ok && (IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) {
|
||||
return nil
|
||||
}
|
||||
return serr
|
||||
}
|
||||
if !dir.IsDir() {
|
||||
// Not a directory; return the error from Remove.
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove contents & return first error.
|
||||
err = nil
|
||||
for {
|
||||
fd, err := Open(path)
|
||||
if err != nil {
|
||||
if IsNotExist(err) {
|
||||
// Already deleted by someone else.
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
const request = 1024
|
||||
names, err1 := fd.Readdirnames(request)
|
||||
|
||||
// Removing files from the directory may have caused
|
||||
// the OS to reshuffle it. Simply calling Readdirnames
|
||||
// again may skip some entries. The only reliable way
|
||||
// to avoid this is to close and re-open the
|
||||
// directory. See issue 20841.
|
||||
fd.Close()
|
||||
|
||||
for _, name := range names {
|
||||
err1 := RemoveAll(path + string(PathSeparator) + name)
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
}
|
||||
|
||||
if err1 == io.EOF {
|
||||
break
|
||||
}
|
||||
// If Readdirnames returned an error, use it.
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
if len(names) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// We don't want to re-open unnecessarily, so if we
|
||||
// got fewer than request names from Readdirnames, try
|
||||
// simply removing the directory now. If that
|
||||
// succeeds, we are done.
|
||||
if len(names) < request {
|
||||
err1 := Remove(path)
|
||||
if err1 == nil || IsNotExist(err1) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// We got some error removing the
|
||||
// directory contents, and since we
|
||||
// read fewer names than we requested
|
||||
// there probably aren't more files to
|
||||
// remove. Don't loop around to read
|
||||
// the directory again. We'll probably
|
||||
// just get the same error.
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove directory.
|
||||
err1 := Remove(path)
|
||||
if err1 == nil || IsNotExist(err1) {
|
||||
return nil
|
||||
}
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -5,7 +5,6 @@
|
||||
package os_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"internal/testenv"
|
||||
"io/ioutil"
|
||||
. "os"
|
||||
@ -76,130 +75,6 @@ func TestMkdirAll(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveAll(t *testing.T) {
|
||||
tmpDir := TempDir()
|
||||
// Work directory.
|
||||
path := tmpDir + "/_TestRemoveAll_"
|
||||
fpath := path + "/file"
|
||||
dpath := path + "/dir"
|
||||
|
||||
// Make directory with 1 file and remove.
|
||||
if err := MkdirAll(path, 0777); err != nil {
|
||||
t.Fatalf("MkdirAll %q: %s", path, err)
|
||||
}
|
||||
fd, err := Create(fpath)
|
||||
if err != nil {
|
||||
t.Fatalf("create %q: %s", fpath, err)
|
||||
}
|
||||
fd.Close()
|
||||
if err = RemoveAll(path); err != nil {
|
||||
t.Fatalf("RemoveAll %q (first): %s", path, err)
|
||||
}
|
||||
if _, err = Lstat(path); err == nil {
|
||||
t.Fatalf("Lstat %q succeeded after RemoveAll (first)", path)
|
||||
}
|
||||
|
||||
// Make directory with file and subdirectory and remove.
|
||||
if err = MkdirAll(dpath, 0777); err != nil {
|
||||
t.Fatalf("MkdirAll %q: %s", dpath, err)
|
||||
}
|
||||
fd, err = Create(fpath)
|
||||
if err != nil {
|
||||
t.Fatalf("create %q: %s", fpath, err)
|
||||
}
|
||||
fd.Close()
|
||||
fd, err = Create(dpath + "/file")
|
||||
if err != nil {
|
||||
t.Fatalf("create %q: %s", fpath, err)
|
||||
}
|
||||
fd.Close()
|
||||
if err = RemoveAll(path); err != nil {
|
||||
t.Fatalf("RemoveAll %q (second): %s", path, err)
|
||||
}
|
||||
if _, err := Lstat(path); err == nil {
|
||||
t.Fatalf("Lstat %q succeeded after RemoveAll (second)", path)
|
||||
}
|
||||
|
||||
// Determine if we should run the following test.
|
||||
testit := true
|
||||
if runtime.GOOS == "windows" {
|
||||
// Chmod is not supported under windows.
|
||||
testit = false
|
||||
} else {
|
||||
// Test fails as root.
|
||||
testit = Getuid() != 0
|
||||
}
|
||||
if testit {
|
||||
// Make directory with file and subdirectory and trigger error.
|
||||
if err = MkdirAll(dpath, 0777); err != nil {
|
||||
t.Fatalf("MkdirAll %q: %s", dpath, err)
|
||||
}
|
||||
|
||||
for _, s := range []string{fpath, dpath + "/file1", path + "/zzz"} {
|
||||
fd, err = Create(s)
|
||||
if err != nil {
|
||||
t.Fatalf("create %q: %s", s, err)
|
||||
}
|
||||
fd.Close()
|
||||
}
|
||||
if err = Chmod(dpath, 0); err != nil {
|
||||
t.Fatalf("Chmod %q 0: %s", dpath, err)
|
||||
}
|
||||
|
||||
// No error checking here: either RemoveAll
|
||||
// will or won't be able to remove dpath;
|
||||
// either way we want to see if it removes fpath
|
||||
// and path/zzz. Reasons why RemoveAll might
|
||||
// succeed in removing dpath as well include:
|
||||
// * running as root
|
||||
// * running on a file system without permissions (FAT)
|
||||
RemoveAll(path)
|
||||
Chmod(dpath, 0777)
|
||||
|
||||
for _, s := range []string{fpath, path + "/zzz"} {
|
||||
if _, err = Lstat(s); err == nil {
|
||||
t.Fatalf("Lstat %q succeeded after partial RemoveAll", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err = RemoveAll(path); err != nil {
|
||||
t.Fatalf("RemoveAll %q after partial RemoveAll: %s", path, err)
|
||||
}
|
||||
if _, err = Lstat(path); err == nil {
|
||||
t.Fatalf("Lstat %q succeeded after RemoveAll (final)", path)
|
||||
}
|
||||
}
|
||||
|
||||
// Test RemoveAll on a large directory.
|
||||
func TestRemoveAllLarge(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
|
||||
tmpDir := TempDir()
|
||||
// Work directory.
|
||||
path := tmpDir + "/_TestRemoveAllLarge_"
|
||||
|
||||
// Make directory with 1000 files and remove.
|
||||
if err := MkdirAll(path, 0777); err != nil {
|
||||
t.Fatalf("MkdirAll %q: %s", path, err)
|
||||
}
|
||||
for i := 0; i < 1000; i++ {
|
||||
fpath := fmt.Sprintf("%s/file%d", path, i)
|
||||
fd, err := Create(fpath)
|
||||
if err != nil {
|
||||
t.Fatalf("create %q: %s", fpath, err)
|
||||
}
|
||||
fd.Close()
|
||||
}
|
||||
if err := RemoveAll(path); err != nil {
|
||||
t.Fatalf("RemoveAll %q: %s", path, err)
|
||||
}
|
||||
if _, err := Lstat(path); err == nil {
|
||||
t.Fatalf("Lstat %q succeeded after RemoveAll", path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMkdirAllWithSymlink(t *testing.T) {
|
||||
testenv.MustHaveSymlink(t)
|
||||
|
||||
|
@ -16,7 +16,7 @@ func IsPathSeparator(c uint8) bool {
|
||||
return PathSeparator == c
|
||||
}
|
||||
|
||||
// basename removes trailing slashes and the leading directory name from path name
|
||||
// basename removes trailing slashes and the leading directory name from path name.
|
||||
func basename(name string) string {
|
||||
i := len(name) - 1
|
||||
// Remove trailing slashes
|
||||
@ -34,6 +34,32 @@ func basename(name string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
// splitPath returns the base name and parent directory.
|
||||
func splitPath(path string) (string, string) {
|
||||
// if no better parent is found, the path is relative from "here"
|
||||
dirname := "."
|
||||
// if no slashes in path, base is path
|
||||
basename := path
|
||||
|
||||
i := len(path) - 1
|
||||
|
||||
// Remove trailing slashes
|
||||
for ; i > 0 && path[i] == '/'; i-- {
|
||||
path = path[:i]
|
||||
}
|
||||
|
||||
// Remove leading directory path
|
||||
for i--; i >= 0; i-- {
|
||||
if path[i] == '/' {
|
||||
dirname = path[:i+1]
|
||||
basename = path[i+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return dirname, basename
|
||||
}
|
||||
|
||||
func fixRootDirectory(p string) string {
|
||||
return p
|
||||
}
|
||||
|
139
src/os/removeall_at.go
Normal file
139
src/os/removeall_at.go
Normal file
@ -0,0 +1,139 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux darwin freebsd openbsd netbsd dragonfly
|
||||
|
||||
package os
|
||||
|
||||
import (
|
||||
"internal/syscall/unix"
|
||||
"io"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func RemoveAll(path string) error {
|
||||
// Not allowed in unix
|
||||
if path == "" || endsWithDot(path) {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
|
||||
// RemoveAll recurses by deleting the path base from
|
||||
// its parent directory
|
||||
parentDir, base := splitPath(path)
|
||||
|
||||
parent, err := Open(parentDir)
|
||||
if IsNotExist(err) {
|
||||
// If parent does not exist, base cannot exist. Fail silently
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer parent.Close()
|
||||
|
||||
return removeAllFrom(parent, base)
|
||||
}
|
||||
|
||||
func removeAllFrom(parent *File, path string) error {
|
||||
parentFd := int(parent.Fd())
|
||||
// Simple case: if Unlink (aka remove) works, we're done.
|
||||
err := unix.Unlinkat(parentFd, path, 0)
|
||||
if err == nil || IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If not a "is directory" error, we have a problem
|
||||
if err != syscall.EISDIR && err != syscall.EPERM {
|
||||
return err
|
||||
}
|
||||
|
||||
// Is this a directory we need to recurse into?
|
||||
var statInfo syscall.Stat_t
|
||||
statErr := unix.Fstatat(parentFd, path, &statInfo, unix.AT_SYMLINK_NOFOLLOW)
|
||||
if statErr != nil {
|
||||
return statErr
|
||||
}
|
||||
if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR {
|
||||
// Not a directory; return the error from the Remove
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the directory's entries
|
||||
var recurseErr error
|
||||
for {
|
||||
const request = 1024
|
||||
|
||||
// Open the directory to recurse into
|
||||
file, err := openFdAt(parentFd, path)
|
||||
if err != nil {
|
||||
if IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
names, readErr := file.Readdirnames(request)
|
||||
// Errors other than EOF should stop us from continuing
|
||||
if readErr != nil && readErr != io.EOF {
|
||||
file.Close()
|
||||
if IsNotExist(readErr) {
|
||||
return nil
|
||||
}
|
||||
return readErr
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
err := removeAllFrom(file, name)
|
||||
if err != nil {
|
||||
recurseErr = err
|
||||
}
|
||||
}
|
||||
|
||||
// Removing files from the directory may have caused
|
||||
// the OS to reshuffle it. Simply calling Readdirnames
|
||||
// again may skip some entries. The only reliable way
|
||||
// to avoid this is to close and re-open the
|
||||
// directory. See issue 20841.
|
||||
file.Close()
|
||||
|
||||
// Finish when the end of the directory is reached
|
||||
if len(names) < request {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the directory itself
|
||||
unlinkError := unix.Unlinkat(parentFd, path, unix.AT_REMOVEDIR)
|
||||
if unlinkError == nil || IsNotExist(unlinkError) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if recurseErr != nil {
|
||||
return recurseErr
|
||||
}
|
||||
return unlinkError
|
||||
}
|
||||
|
||||
func openFdAt(fd int, path string) (*File, error) {
|
||||
fd, err := unix.Openat(fd, path, O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewFile(uintptr(fd), path), nil
|
||||
}
|
||||
|
||||
func endsWithDot(path string) bool {
|
||||
if path == "." || path == ".." {
|
||||
return true
|
||||
}
|
||||
if len(path) >= 2 && path[len(path)-2:] == "/." {
|
||||
return true
|
||||
}
|
||||
if len(path) >= 3 && path[len(path)-3:] == "/.." {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
110
src/os/removeall_noat.go
Normal file
110
src/os/removeall_noat.go
Normal file
@ -0,0 +1,110 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !linux,!darwin,!freebsd,!openbsd,!netbsd,!dragonfly
|
||||
|
||||
package os
|
||||
|
||||
import (
|
||||
"io"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// RemoveAll removes path and any children it contains.
|
||||
// It removes everything it can but returns the first error
|
||||
// it encounters. If the path does not exist, RemoveAll
|
||||
// returns nil (no error).
|
||||
func RemoveAll(path string) error {
|
||||
// Simple case: if Remove works, we're done.
|
||||
err := Remove(path)
|
||||
if err == nil || IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Otherwise, is this a directory we need to recurse into?
|
||||
dir, serr := Lstat(path)
|
||||
if serr != nil {
|
||||
if serr, ok := serr.(*PathError); ok && (IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) {
|
||||
return nil
|
||||
}
|
||||
return serr
|
||||
}
|
||||
if !dir.IsDir() {
|
||||
// Not a directory; return the error from Remove.
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove contents & return first error.
|
||||
err = nil
|
||||
for {
|
||||
fd, err := Open(path)
|
||||
if err != nil {
|
||||
if IsNotExist(err) {
|
||||
// Already deleted by someone else.
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
const request = 1024
|
||||
names, err1 := fd.Readdirnames(request)
|
||||
|
||||
// Removing files from the directory may have caused
|
||||
// the OS to reshuffle it. Simply calling Readdirnames
|
||||
// again may skip some entries. The only reliable way
|
||||
// to avoid this is to close and re-open the
|
||||
// directory. See issue 20841.
|
||||
fd.Close()
|
||||
|
||||
for _, name := range names {
|
||||
err1 := RemoveAll(path + string(PathSeparator) + name)
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
}
|
||||
|
||||
if err1 == io.EOF {
|
||||
break
|
||||
}
|
||||
// If Readdirnames returned an error, use it.
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
if len(names) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// We don't want to re-open unnecessarily, so if we
|
||||
// got fewer than request names from Readdirnames, try
|
||||
// simply removing the directory now. If that
|
||||
// succeeds, we are done.
|
||||
if len(names) < request {
|
||||
err1 := Remove(path)
|
||||
if err1 == nil || IsNotExist(err1) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// We got some error removing the
|
||||
// directory contents, and since we
|
||||
// read fewer names than we requested
|
||||
// there probably aren't more files to
|
||||
// remove. Don't loop around to read
|
||||
// the directory again. We'll probably
|
||||
// just get the same error.
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove directory.
|
||||
err1 := Remove(path)
|
||||
if err1 == nil || IsNotExist(err1) {
|
||||
return nil
|
||||
}
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
237
src/os/removeall_test.go
Normal file
237
src/os/removeall_test.go
Normal file
@ -0,0 +1,237 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package os_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
. "os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRemoveAll(t *testing.T) {
|
||||
tmpDir := TempDir()
|
||||
// Work directory.
|
||||
file := "file"
|
||||
path := tmpDir + "/_TestRemoveAll_"
|
||||
fpath := path + "/file"
|
||||
dpath := path + "/dir"
|
||||
|
||||
// Make a regular file and remove
|
||||
fd, err := Create(file)
|
||||
if err != nil {
|
||||
t.Fatalf("create %q: %s", file, err)
|
||||
}
|
||||
fd.Close()
|
||||
if err = RemoveAll(file); err != nil {
|
||||
t.Fatalf("RemoveAll %q (first): %s", file, err)
|
||||
}
|
||||
if _, err = Lstat(file); err == nil {
|
||||
t.Fatalf("Lstat %q succeeded after RemoveAll (first)", file)
|
||||
}
|
||||
|
||||
// Make directory with 1 file and remove.
|
||||
if err := MkdirAll(path, 0777); err != nil {
|
||||
t.Fatalf("MkdirAll %q: %s", path, err)
|
||||
}
|
||||
fd, err = Create(fpath)
|
||||
if err != nil {
|
||||
t.Fatalf("create %q: %s", fpath, err)
|
||||
}
|
||||
fd.Close()
|
||||
if err = RemoveAll(path); err != nil {
|
||||
t.Fatalf("RemoveAll %q (second): %s", path, err)
|
||||
}
|
||||
if _, err = Lstat(path); err == nil {
|
||||
t.Fatalf("Lstat %q succeeded after RemoveAll (second)", path)
|
||||
}
|
||||
|
||||
// Make directory with file and subdirectory and remove.
|
||||
if err = MkdirAll(dpath, 0777); err != nil {
|
||||
t.Fatalf("MkdirAll %q: %s", dpath, err)
|
||||
}
|
||||
fd, err = Create(fpath)
|
||||
if err != nil {
|
||||
t.Fatalf("create %q: %s", fpath, err)
|
||||
}
|
||||
fd.Close()
|
||||
fd, err = Create(dpath + "/file")
|
||||
if err != nil {
|
||||
t.Fatalf("create %q: %s", fpath, err)
|
||||
}
|
||||
fd.Close()
|
||||
if err = RemoveAll(path); err != nil {
|
||||
t.Fatalf("RemoveAll %q (third): %s", path, err)
|
||||
}
|
||||
if _, err := Lstat(path); err == nil {
|
||||
t.Fatalf("Lstat %q succeeded after RemoveAll (third)", path)
|
||||
}
|
||||
|
||||
// Determine if we should run the following test.
|
||||
testit := true
|
||||
if runtime.GOOS == "windows" {
|
||||
// Chmod is not supported under windows.
|
||||
testit = false
|
||||
} else {
|
||||
// Test fails as root.
|
||||
testit = Getuid() != 0
|
||||
}
|
||||
if testit {
|
||||
// Make directory with file and subdirectory and trigger error.
|
||||
if err = MkdirAll(dpath, 0777); err != nil {
|
||||
t.Fatalf("MkdirAll %q: %s", dpath, err)
|
||||
}
|
||||
|
||||
for _, s := range []string{fpath, dpath + "/file1", path + "/zzz"} {
|
||||
fd, err = Create(s)
|
||||
if err != nil {
|
||||
t.Fatalf("create %q: %s", s, err)
|
||||
}
|
||||
fd.Close()
|
||||
}
|
||||
if err = Chmod(dpath, 0); err != nil {
|
||||
t.Fatalf("Chmod %q 0: %s", dpath, err)
|
||||
}
|
||||
|
||||
// No error checking here: either RemoveAll
|
||||
// will or won't be able to remove dpath;
|
||||
// either way we want to see if it removes fpath
|
||||
// and path/zzz. Reasons why RemoveAll might
|
||||
// succeed in removing dpath as well include:
|
||||
// * running as root
|
||||
// * running on a file system without permissions (FAT)
|
||||
RemoveAll(path)
|
||||
Chmod(dpath, 0777)
|
||||
|
||||
for _, s := range []string{fpath, path + "/zzz"} {
|
||||
if _, err = Lstat(s); err == nil {
|
||||
t.Fatalf("Lstat %q succeeded after partial RemoveAll", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err = RemoveAll(path); err != nil {
|
||||
t.Fatalf("RemoveAll %q after partial RemoveAll: %s", path, err)
|
||||
}
|
||||
if _, err = Lstat(path); err == nil {
|
||||
t.Fatalf("Lstat %q succeeded after RemoveAll (final)", path)
|
||||
}
|
||||
}
|
||||
|
||||
// Test RemoveAll on a large directory.
|
||||
func TestRemoveAllLarge(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping in short mode")
|
||||
}
|
||||
|
||||
tmpDir := TempDir()
|
||||
// Work directory.
|
||||
path := tmpDir + "/_TestRemoveAllLarge_"
|
||||
|
||||
// Make directory with 1000 files and remove.
|
||||
if err := MkdirAll(path, 0777); err != nil {
|
||||
t.Fatalf("MkdirAll %q: %s", path, err)
|
||||
}
|
||||
for i := 0; i < 1000; i++ {
|
||||
fpath := fmt.Sprintf("%s/file%d", path, i)
|
||||
fd, err := Create(fpath)
|
||||
if err != nil {
|
||||
t.Fatalf("create %q: %s", fpath, err)
|
||||
}
|
||||
fd.Close()
|
||||
}
|
||||
if err := RemoveAll(path); err != nil {
|
||||
t.Fatalf("RemoveAll %q: %s", path, err)
|
||||
}
|
||||
if _, err := Lstat(path); err == nil {
|
||||
t.Fatalf("Lstat %q succeeded after RemoveAll", path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveAllLongPath(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "linux", "darwin", "freebsd", "openbsd", "netbsd", "dragonfly":
|
||||
break
|
||||
default:
|
||||
t.Skip("skipping for not implemented platforms")
|
||||
}
|
||||
|
||||
prevDir, err := Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("Could not get wd: %s", err)
|
||||
}
|
||||
|
||||
startPath, err := ioutil.TempDir("", "TestRemoveAllLongPath-")
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create TempDir: %s", err)
|
||||
}
|
||||
err = Chdir(startPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not chdir %s: %s", startPath, err)
|
||||
}
|
||||
|
||||
// Removing paths with over 4096 chars commonly fails
|
||||
for i := 0; i < 41; i++ {
|
||||
name := strings.Repeat("a", 100)
|
||||
|
||||
err = Mkdir(name, 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not mkdir %s: %s", name, err)
|
||||
}
|
||||
|
||||
err = Chdir(name)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not chdir %s: %s", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = Chdir(prevDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not chdir %s: %s", prevDir, err)
|
||||
}
|
||||
|
||||
err = RemoveAll(startPath)
|
||||
if err != nil {
|
||||
t.Errorf("RemoveAll could not remove long file path %s: %s", startPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveAllDot(t *testing.T) {
|
||||
switch runtime.GOOS {
|
||||
case "linux", "darwin", "freebsd", "openbsd", "netbsd", "dragonfly":
|
||||
break
|
||||
default:
|
||||
t.Skip("skipping for not implemented platforms")
|
||||
}
|
||||
|
||||
prevDir, err := Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("Could not get wd: %s", err)
|
||||
}
|
||||
tempDir, err := ioutil.TempDir("", "TestRemoveAllDot-")
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create TempDir: %s", err)
|
||||
}
|
||||
err = Chdir(tempDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not chdir to tempdir: %s", err)
|
||||
}
|
||||
|
||||
err = RemoveAll(".")
|
||||
if err == nil {
|
||||
t.Errorf("RemoveAll succeed to remove .")
|
||||
}
|
||||
|
||||
err = RemoveAll("..")
|
||||
if err == nil {
|
||||
t.Errorf("RemoveAll succeed to remove ..")
|
||||
}
|
||||
|
||||
err = Chdir(prevDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not chdir %s: %s", prevDir, err)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user