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 hurd linux netbsd openbsd solaris
6
7package os
8
9import (
10	"internal/syscall/unix"
11	"io"
12	"runtime"
13	"syscall"
14)
15
16func 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
59func 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	if unlinkError == nil || IsNotExist(unlinkError) {
157		return nil
158	}
159
160	if recurseErr != nil {
161		return recurseErr
162	}
163	return &PathError{"unlinkat", base, unlinkError}
164}
165
166// openFdAt opens path relative to the directory in fd.
167// Other than that this should act like openFileNolog.
168// This acts like openFileNolog rather than OpenFile because
169// we are going to (try to) remove the file.
170// The contents of this file are not relevant for test caching.
171func openFdAt(dirfd int, name string) (*File, error) {
172	var r int
173	for {
174		var e error
175		r, e = unix.Openat(dirfd, name, O_RDONLY|syscall.O_CLOEXEC, 0)
176		if e == nil {
177			break
178		}
179
180		// See comment in openFileNolog.
181		if runtime.GOOS == "darwin" && e == syscall.EINTR {
182			continue
183		}
184
185		return nil, e
186	}
187
188	if !supportsCloseOnExec {
189		syscall.CloseOnExec(r)
190	}
191
192	return newFile(uintptr(r), name, kindOpenFile), nil
193}
194