...

Source file src/os/removeall_at.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	// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
     6	
     7	package os
     8	
     9	import (
    10		"internal/syscall/unix"
    11		"io"
    12		"runtime"
    13		"syscall"
    14	)
    15	
    16	func removeAll(path string) error {
    17		if path == "" {
    18			// fail silently to retain compatibility with previous behavior
    19			// of RemoveAll. See issue 28830.
    20			return nil
    21		}
    22	
    23		// The rmdir system call does not permit removing ".",
    24		// so we don't permit it either.
    25		if endsWithDot(path) {
    26			return &PathError{"RemoveAll", path, syscall.EINVAL}
    27		}
    28	
    29		// Simple case: if Remove works, we're done.
    30		err := Remove(path)
    31		if err == nil || IsNotExist(err) {
    32			return nil
    33		}
    34	
    35		// RemoveAll recurses by deleting the path base from
    36		// its parent directory
    37		parentDir, base := splitPath(path)
    38	
    39		parent, err := Open(parentDir)
    40		if IsNotExist(err) {
    41			// If parent does not exist, base cannot exist. Fail silently
    42			return nil
    43		}
    44		if err != nil {
    45			return err
    46		}
    47		defer parent.Close()
    48	
    49		if err := removeAllFrom(parent, base); err != nil {
    50			if pathErr, ok := err.(*PathError); ok {
    51				pathErr.Path = parentDir + string(PathSeparator) + pathErr.Path
    52				err = pathErr
    53			}
    54			return err
    55		}
    56		return nil
    57	}
    58	
    59	func removeAllFrom(parent *File, base string) error {
    60		parentFd := int(parent.Fd())
    61		// Simple case: if Unlink (aka remove) works, we're done.
    62		err := unix.Unlinkat(parentFd, base, 0)
    63		if err == nil || IsNotExist(err) {
    64			return nil
    65		}
    66	
    67		// EISDIR means that we have a directory, and we need to
    68		// remove its contents.
    69		// EPERM or EACCES means that we don't have write permission on
    70		// the parent directory, but this entry might still be a directory
    71		// whose contents need to be removed.
    72		// Otherwise just return the error.
    73		if err != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES {
    74			return &PathError{"unlinkat", base, err}
    75		}
    76	
    77		// Is this a directory we need to recurse into?
    78		var statInfo syscall.Stat_t
    79		statErr := unix.Fstatat(parentFd, base, &statInfo, unix.AT_SYMLINK_NOFOLLOW)
    80		if statErr != nil {
    81			if IsNotExist(statErr) {
    82				return nil
    83			}
    84			return &PathError{"fstatat", base, statErr}
    85		}
    86		if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR {
    87			// Not a directory; return the error from the unix.Unlinkat.
    88			return &PathError{"unlinkat", base, err}
    89		}
    90	
    91		// Remove the directory's entries.
    92		var recurseErr error
    93		for {
    94			const reqSize = 1024
    95			var respSize int
    96	
    97			// Open the directory to recurse into
    98			file, err := openFdAt(parentFd, base)
    99			if err != nil {
   100				if IsNotExist(err) {
   101					return nil
   102				}
   103				recurseErr = &PathError{"openfdat", base, err}
   104				break
   105			}
   106	
   107			for {
   108				numErr := 0
   109	
   110				names, readErr := file.Readdirnames(reqSize)
   111				// Errors other than EOF should stop us from continuing.
   112				if readErr != nil && readErr != io.EOF {
   113					file.Close()
   114					if IsNotExist(readErr) {
   115						return nil
   116					}
   117					return &PathError{"readdirnames", base, readErr}
   118				}
   119	
   120				respSize = len(names)
   121				for _, name := range names {
   122					err := removeAllFrom(file, name)
   123					if err != nil {
   124						if pathErr, ok := err.(*PathError); ok {
   125							pathErr.Path = base + string(PathSeparator) + pathErr.Path
   126						}
   127						numErr++
   128						if recurseErr == nil {
   129							recurseErr = err
   130						}
   131					}
   132				}
   133	
   134				// If we can delete any entry, break to start new iteration.
   135				// Otherwise, we discard current names, get next entries and try deleting them.
   136				if numErr != reqSize {
   137					break
   138				}
   139			}
   140	
   141			// Removing files from the directory may have caused
   142			// the OS to reshuffle it. Simply calling Readdirnames
   143			// again may skip some entries. The only reliable way
   144			// to avoid this is to close and re-open the
   145			// directory. See issue 20841.
   146			file.Close()
   147	
   148			// Finish when the end of the directory is reached
   149			if respSize < reqSize {
   150				break
   151			}
   152		}
   153	
   154		// Remove the directory itself.
   155		unlinkError := unix.Unlinkat(parentFd, base, unix.AT_REMOVEDIR)
   156		unlinkError = removeAllTestHook(unlinkError)
   157		if unlinkError == nil || IsNotExist(unlinkError) {
   158			return nil
   159		}
   160	
   161		if recurseErr != nil {
   162			return recurseErr
   163		}
   164		return &PathError{"unlinkat", base, unlinkError}
   165	}
   166	
   167	// openFdAt opens path relative to the directory in fd.
   168	// Other than that this should act like openFileNolog.
   169	// This acts like openFileNolog rather than OpenFile because
   170	// we are going to (try to) remove the file.
   171	// The contents of this file are not relevant for test caching.
   172	func openFdAt(dirfd int, name string) (*File, error) {
   173		var r int
   174		for {
   175			var e error
   176			r, e = unix.Openat(dirfd, name, O_RDONLY|syscall.O_CLOEXEC, 0)
   177			if e == nil {
   178				break
   179			}
   180	
   181			// See comment in openFileNolog.
   182			if runtime.GOOS == "darwin" && e == syscall.EINTR {
   183				continue
   184			}
   185	
   186			return nil, e
   187		}
   188	
   189		if !supportsCloseOnExec {
   190			syscall.CloseOnExec(r)
   191		}
   192	
   193		return newFile(uintptr(r), name, kindOpenFile), nil
   194	}
   195	

View as plain text