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