Source file src/os/removeall_test.go

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package os_test
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"internal/testenv"
    11  	. "os"
    12  	"path/filepath"
    13  	"runtime"
    14  	"strconv"
    15  	"strings"
    16  	"testing"
    17  )
    18  
    19  func TestRemoveAll(t *testing.T) {
    20  	t.Parallel()
    21  
    22  	tmpDir := t.TempDir()
    23  	if err := RemoveAll(""); err != nil {
    24  		t.Errorf("RemoveAll(\"\"): %v; want nil", err)
    25  	}
    26  
    27  	file := filepath.Join(tmpDir, "file")
    28  	path := filepath.Join(tmpDir, "_TestRemoveAll_")
    29  	fpath := filepath.Join(path, "file")
    30  	dpath := filepath.Join(path, "dir")
    31  
    32  	// Make a regular file and remove
    33  	fd, err := Create(file)
    34  	if err != nil {
    35  		t.Fatalf("create %q: %s", file, err)
    36  	}
    37  	fd.Close()
    38  	if err = RemoveAll(file); err != nil {
    39  		t.Fatalf("RemoveAll %q (first): %s", file, err)
    40  	}
    41  	if _, err = Lstat(file); err == nil {
    42  		t.Fatalf("Lstat %q succeeded after RemoveAll (first)", file)
    43  	}
    44  
    45  	// Make directory with 1 file and remove.
    46  	if err := MkdirAll(path, 0777); err != nil {
    47  		t.Fatalf("MkdirAll %q: %s", path, err)
    48  	}
    49  	fd, err = Create(fpath)
    50  	if err != nil {
    51  		t.Fatalf("create %q: %s", fpath, err)
    52  	}
    53  	fd.Close()
    54  	if err = RemoveAll(path); err != nil {
    55  		t.Fatalf("RemoveAll %q (second): %s", path, err)
    56  	}
    57  	if _, err = Lstat(path); err == nil {
    58  		t.Fatalf("Lstat %q succeeded after RemoveAll (second)", path)
    59  	}
    60  
    61  	// Make directory with file and subdirectory and remove.
    62  	if err = MkdirAll(dpath, 0777); err != nil {
    63  		t.Fatalf("MkdirAll %q: %s", dpath, err)
    64  	}
    65  	fd, err = Create(fpath)
    66  	if err != nil {
    67  		t.Fatalf("create %q: %s", fpath, err)
    68  	}
    69  	fd.Close()
    70  	fd, err = Create(dpath + "/file")
    71  	if err != nil {
    72  		t.Fatalf("create %q: %s", fpath, err)
    73  	}
    74  	fd.Close()
    75  	if err = RemoveAll(path); err != nil {
    76  		t.Fatalf("RemoveAll %q (third): %s", path, err)
    77  	}
    78  	if _, err := Lstat(path); err == nil {
    79  		t.Fatalf("Lstat %q succeeded after RemoveAll (third)", path)
    80  	}
    81  
    82  	// Chmod is not supported under Windows or wasip1 and test fails as root.
    83  	if runtime.GOOS != "windows" && runtime.GOOS != "wasip1" && Getuid() != 0 {
    84  		// Make directory with file and subdirectory and trigger error.
    85  		if err = MkdirAll(dpath, 0777); err != nil {
    86  			t.Fatalf("MkdirAll %q: %s", dpath, err)
    87  		}
    88  
    89  		for _, s := range []string{fpath, dpath + "/file1", path + "/zzz"} {
    90  			fd, err = Create(s)
    91  			if err != nil {
    92  				t.Fatalf("create %q: %s", s, err)
    93  			}
    94  			fd.Close()
    95  		}
    96  		if err = Chmod(dpath, 0); err != nil {
    97  			t.Fatalf("Chmod %q 0: %s", dpath, err)
    98  		}
    99  
   100  		// No error checking here: either RemoveAll
   101  		// will or won't be able to remove dpath;
   102  		// either way we want to see if it removes fpath
   103  		// and path/zzz. Reasons why RemoveAll might
   104  		// succeed in removing dpath as well include:
   105  		//	* running as root
   106  		//	* running on a file system without permissions (FAT)
   107  		RemoveAll(path)
   108  		Chmod(dpath, 0777)
   109  
   110  		for _, s := range []string{fpath, path + "/zzz"} {
   111  			if _, err = Lstat(s); err == nil {
   112  				t.Fatalf("Lstat %q succeeded after partial RemoveAll", s)
   113  			}
   114  		}
   115  	}
   116  	if err = RemoveAll(path); err != nil {
   117  		t.Fatalf("RemoveAll %q after partial RemoveAll: %s", path, err)
   118  	}
   119  	if _, err = Lstat(path); err == nil {
   120  		t.Fatalf("Lstat %q succeeded after RemoveAll (final)", path)
   121  	}
   122  }
   123  
   124  // Test RemoveAll on a large directory.
   125  func TestRemoveAllLarge(t *testing.T) {
   126  	if testing.Short() {
   127  		t.Skip("skipping in short mode")
   128  	}
   129  	t.Parallel()
   130  
   131  	tmpDir := t.TempDir()
   132  	path := filepath.Join(tmpDir, "_TestRemoveAllLarge_")
   133  
   134  	// Make directory with 1000 files and remove.
   135  	if err := MkdirAll(path, 0777); err != nil {
   136  		t.Fatalf("MkdirAll %q: %s", path, err)
   137  	}
   138  	for i := 0; i < 1000; i++ {
   139  		fpath := fmt.Sprintf("%s/file%d", path, i)
   140  		fd, err := Create(fpath)
   141  		if err != nil {
   142  			t.Fatalf("create %q: %s", fpath, err)
   143  		}
   144  		fd.Close()
   145  	}
   146  	if err := RemoveAll(path); err != nil {
   147  		t.Fatalf("RemoveAll %q: %s", path, err)
   148  	}
   149  	if _, err := Lstat(path); err == nil {
   150  		t.Fatalf("Lstat %q succeeded after RemoveAll", path)
   151  	}
   152  }
   153  
   154  func TestRemoveAllLongPath(t *testing.T) {
   155  	switch runtime.GOOS {
   156  	case "aix", "darwin", "ios", "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "illumos", "solaris":
   157  		break
   158  	default:
   159  		t.Skip("skipping for not implemented platforms")
   160  	}
   161  
   162  	startPath := t.TempDir()
   163  	t.Chdir(startPath)
   164  
   165  	// Removing paths with over 4096 chars commonly fails.
   166  	name := strings.Repeat("a", 100)
   167  	for i := 0; i < 41; i++ {
   168  		if err := Mkdir(name, 0755); err != nil {
   169  			t.Fatalf("Could not mkdir %s: %s", name, err)
   170  		}
   171  		if err := Chdir(name); err != nil {
   172  			t.Fatalf("Could not chdir %s: %s", name, err)
   173  		}
   174  	}
   175  
   176  	// Chdir out of startPath before attempting to remove it,
   177  	// otherwise RemoveAll fails on aix, illumos and solaris.
   178  	err := Chdir(filepath.Join(startPath, ".."))
   179  	if err != nil {
   180  		t.Fatalf("Could not chdir: %s", err)
   181  	}
   182  
   183  	err = RemoveAll(startPath)
   184  	if err != nil {
   185  		t.Errorf("RemoveAll could not remove long file path %s: %s", startPath, err)
   186  	}
   187  }
   188  
   189  func TestRemoveAllDot(t *testing.T) {
   190  	t.Chdir(t.TempDir())
   191  
   192  	if err := RemoveAll("."); err == nil {
   193  		t.Errorf("RemoveAll succeed to remove .")
   194  	}
   195  }
   196  
   197  func TestRemoveAllDotDot(t *testing.T) {
   198  	t.Parallel()
   199  
   200  	tempDir := t.TempDir()
   201  	subdir := filepath.Join(tempDir, "x")
   202  	subsubdir := filepath.Join(subdir, "y")
   203  	if err := MkdirAll(subsubdir, 0777); err != nil {
   204  		t.Fatal(err)
   205  	}
   206  	if err := RemoveAll(filepath.Join(subsubdir, "..")); err != nil {
   207  		t.Error(err)
   208  	}
   209  	for _, dir := range []string{subsubdir, subdir} {
   210  		if _, err := Stat(dir); err == nil {
   211  			t.Errorf("%s: exists after RemoveAll", dir)
   212  		}
   213  	}
   214  }
   215  
   216  // Issue #29178.
   217  func TestRemoveReadOnlyDir(t *testing.T) {
   218  	t.Parallel()
   219  
   220  	tempDir := t.TempDir()
   221  	subdir := filepath.Join(tempDir, "x")
   222  	if err := Mkdir(subdir, 0); err != nil {
   223  		t.Fatal(err)
   224  	}
   225  
   226  	// If an error occurs make it more likely that removing the
   227  	// temporary directory will succeed.
   228  	defer Chmod(subdir, 0777)
   229  
   230  	if err := RemoveAll(subdir); err != nil {
   231  		t.Fatal(err)
   232  	}
   233  
   234  	if _, err := Stat(subdir); err == nil {
   235  		t.Error("subdirectory was not removed")
   236  	}
   237  }
   238  
   239  // Issue #29983.
   240  func TestRemoveAllButReadOnlyAndPathError(t *testing.T) {
   241  	switch runtime.GOOS {
   242  	case "js", "wasip1", "windows":
   243  		t.Skipf("skipping test on %s", runtime.GOOS)
   244  	}
   245  
   246  	if Getuid() == 0 {
   247  		t.Skip("skipping test when running as root")
   248  	}
   249  
   250  	t.Parallel()
   251  
   252  	tempDir := t.TempDir()
   253  	dirs := []string{
   254  		"a",
   255  		"a/x",
   256  		"a/x/1",
   257  		"b",
   258  		"b/y",
   259  		"b/y/2",
   260  		"c",
   261  		"c/z",
   262  		"c/z/3",
   263  	}
   264  	readonly := []string{
   265  		"b",
   266  	}
   267  	inReadonly := func(d string) bool {
   268  		for _, ro := range readonly {
   269  			if d == ro {
   270  				return true
   271  			}
   272  			dd, _ := filepath.Split(d)
   273  			if filepath.Clean(dd) == ro {
   274  				return true
   275  			}
   276  		}
   277  		return false
   278  	}
   279  
   280  	for _, dir := range dirs {
   281  		if err := Mkdir(filepath.Join(tempDir, dir), 0777); err != nil {
   282  			t.Fatal(err)
   283  		}
   284  	}
   285  	for _, dir := range readonly {
   286  		d := filepath.Join(tempDir, dir)
   287  		if err := Chmod(d, 0555); err != nil {
   288  			t.Fatal(err)
   289  		}
   290  
   291  		// Defer changing the mode back so that the deferred
   292  		// RemoveAll(tempDir) can succeed.
   293  		defer Chmod(d, 0777)
   294  	}
   295  
   296  	err := RemoveAll(tempDir)
   297  	if err == nil {
   298  		t.Fatal("RemoveAll succeeded unexpectedly")
   299  	}
   300  
   301  	// The error should be of type *PathError.
   302  	// see issue 30491 for details.
   303  	if pathErr, ok := err.(*PathError); ok {
   304  		want := filepath.Join(tempDir, "b", "y")
   305  		if pathErr.Path != want {
   306  			t.Errorf("RemoveAll(%q): err.Path=%q, want %q", tempDir, pathErr.Path, want)
   307  		}
   308  	} else {
   309  		t.Errorf("RemoveAll(%q): error has type %T, want *fs.PathError", tempDir, err)
   310  	}
   311  
   312  	for _, dir := range dirs {
   313  		_, err := Stat(filepath.Join(tempDir, dir))
   314  		if inReadonly(dir) {
   315  			if err != nil {
   316  				t.Errorf("file %q was deleted but should still exist", dir)
   317  			}
   318  		} else {
   319  			if err == nil {
   320  				t.Errorf("file %q still exists but should have been deleted", dir)
   321  			}
   322  		}
   323  	}
   324  }
   325  
   326  func TestRemoveUnreadableDir(t *testing.T) {
   327  	switch runtime.GOOS {
   328  	case "js":
   329  		t.Skipf("skipping test on %s", runtime.GOOS)
   330  	}
   331  
   332  	if Getuid() == 0 {
   333  		t.Skip("skipping test when running as root")
   334  	}
   335  
   336  	t.Parallel()
   337  
   338  	tempDir := t.TempDir()
   339  	target := filepath.Join(tempDir, "d0", "d1", "d2")
   340  	if err := MkdirAll(target, 0755); err != nil {
   341  		t.Fatal(err)
   342  	}
   343  	if err := Chmod(target, 0300); err != nil {
   344  		t.Fatal(err)
   345  	}
   346  	if err := RemoveAll(filepath.Join(tempDir, "d0")); err != nil {
   347  		t.Fatal(err)
   348  	}
   349  }
   350  
   351  // Issue 29921
   352  func TestRemoveAllWithMoreErrorThanReqSize(t *testing.T) {
   353  	if testing.Short() {
   354  		t.Skip("skipping in short mode")
   355  	}
   356  	t.Parallel()
   357  
   358  	tmpDir := t.TempDir()
   359  	path := filepath.Join(tmpDir, "_TestRemoveAllWithMoreErrorThanReqSize_")
   360  
   361  	// Make directory with 1025 read-only files.
   362  	if err := MkdirAll(path, 0777); err != nil {
   363  		t.Fatalf("MkdirAll %q: %s", path, err)
   364  	}
   365  	for i := 0; i < 1025; i++ {
   366  		fpath := filepath.Join(path, fmt.Sprintf("file%d", i))
   367  		fd, err := Create(fpath)
   368  		if err != nil {
   369  			t.Fatalf("create %q: %s", fpath, err)
   370  		}
   371  		fd.Close()
   372  	}
   373  
   374  	// Make the parent directory read-only. On some platforms, this is what
   375  	// prevents Remove from removing the files within that directory.
   376  	if err := Chmod(path, 0555); err != nil {
   377  		t.Fatal(err)
   378  	}
   379  	defer Chmod(path, 0755)
   380  
   381  	// This call should not hang, even on a platform that disallows file deletion
   382  	// from read-only directories.
   383  	err := RemoveAll(path)
   384  
   385  	if Getuid() == 0 {
   386  		// On many platforms, root can remove files from read-only directories.
   387  		return
   388  	}
   389  	if err == nil {
   390  		if runtime.GOOS == "windows" || runtime.GOOS == "wasip1" {
   391  			// Marking a directory as read-only in Windows does not prevent the RemoveAll
   392  			// from creating or removing files within it.
   393  			//
   394  			// For wasip1, there is no support for file permissions so we cannot prevent
   395  			// RemoveAll from removing the files.
   396  			return
   397  		}
   398  		t.Fatal("RemoveAll(<read-only directory>) = nil; want error")
   399  	}
   400  
   401  	dir, err := Open(path)
   402  	if err != nil {
   403  		t.Fatal(err)
   404  	}
   405  	defer dir.Close()
   406  
   407  	names, _ := dir.Readdirnames(1025)
   408  	if len(names) < 1025 {
   409  		t.Fatalf("RemoveAll(<read-only directory>) unexpectedly removed %d read-only files from that directory", 1025-len(names))
   410  	}
   411  }
   412  
   413  func TestRemoveAllNoFcntl(t *testing.T) {
   414  	if testing.Short() {
   415  		t.Skip("skipping in short mode")
   416  	}
   417  
   418  	const env = "GO_TEST_REMOVE_ALL_NO_FCNTL"
   419  	if dir := Getenv(env); dir != "" {
   420  		if err := RemoveAll(dir); err != nil {
   421  			t.Fatal(err)
   422  		}
   423  		return
   424  	}
   425  
   426  	// Only test on Linux so that we can assume we have strace.
   427  	// The code is OS-independent so if it passes on Linux
   428  	// it should pass on other Unix systems.
   429  	if runtime.GOOS != "linux" {
   430  		t.Skipf("skipping test on %s", runtime.GOOS)
   431  	}
   432  	if _, err := Stat("/bin/strace"); err != nil {
   433  		t.Skipf("skipping test because /bin/strace not found: %v", err)
   434  	}
   435  	me, err := Executable()
   436  	if err != nil {
   437  		t.Skipf("skipping because Executable failed: %v", err)
   438  	}
   439  
   440  	// Create 100 directories.
   441  	// The test is that we can remove them without calling fcntl
   442  	// on each one.
   443  	tmpdir := t.TempDir()
   444  	subdir := filepath.Join(tmpdir, "subdir")
   445  	if err := Mkdir(subdir, 0o755); err != nil {
   446  		t.Fatal(err)
   447  	}
   448  	for i := 0; i < 100; i++ {
   449  		subsubdir := filepath.Join(subdir, strconv.Itoa(i))
   450  		if err := Mkdir(filepath.Join(subdir, strconv.Itoa(i)), 0o755); err != nil {
   451  			t.Fatal(err)
   452  		}
   453  		if err := WriteFile(filepath.Join(subsubdir, "file"), nil, 0o644); err != nil {
   454  			t.Fatal(err)
   455  		}
   456  	}
   457  
   458  	cmd := testenv.Command(t, "/bin/strace", "-f", "-e", "fcntl", me, "-test.run=^TestRemoveAllNoFcntl$")
   459  	cmd = testenv.CleanCmdEnv(cmd)
   460  	cmd.Env = append(cmd.Env, env+"="+subdir)
   461  	out, err := cmd.CombinedOutput()
   462  	if len(out) > 0 {
   463  		t.Logf("%s", out)
   464  	}
   465  	if err != nil {
   466  		t.Fatal(err)
   467  	}
   468  
   469  	if got := bytes.Count(out, []byte("fcntl")); got >= 100 {
   470  		t.Errorf("found %d fcntl calls, want < 100", got)
   471  	}
   472  }
   473  
   474  func BenchmarkRemoveAll(b *testing.B) {
   475  	tmpDir := filepath.Join(b.TempDir(), "target")
   476  	b.ReportAllocs()
   477  	b.ResetTimer()
   478  	for i := 0; i < b.N; i++ {
   479  		b.StopTimer()
   480  		err := CopyFS(tmpDir, DirFS("."))
   481  		if err != nil {
   482  			b.Fatal(err)
   483  		}
   484  		b.StartTimer()
   485  		if err := RemoveAll(tmpDir); err != nil {
   486  			b.Fatal(err)
   487  		}
   488  	}
   489  }
   490  

View as plain text