Skip to content

Commit f267de5

Browse files
ostenbomgcapizziJulia Nedialkova
committed
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 openbsd. Not yet implemented on freebsd due to fstatat 64-bit inode compatibility issues. Fixes #27029 Co-authored-by: Giuseppe Capizzi <[email protected]> Co-authored-by: Julia Nedialkova <[email protected]>
1 parent 930ce09 commit f267de5

16 files changed

+685
-225
lines changed

src/internal/syscall/unix/at.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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 linux darwin freebsd openbsd netbsd dragonfly
6+
7+
package unix
8+
9+
import (
10+
"syscall"
11+
"unsafe"
12+
)
13+
14+
func Unlinkat(dirfd int, path string, flags int) error {
15+
var p *byte
16+
p, err := syscall.BytePtrFromString(path)
17+
if err != nil {
18+
return err
19+
}
20+
21+
_, _, errno := syscall.Syscall(unlinkatTrap, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags))
22+
if errno != 0 {
23+
return errno
24+
}
25+
26+
return nil
27+
}
28+
29+
func Openat(dirfd int, path string, flags int, perm uint32) (int, error) {
30+
var p *byte
31+
p, err := syscall.BytePtrFromString(path)
32+
if err != nil {
33+
return 0, err
34+
}
35+
36+
fd, _, errno := syscall.Syscall6(openatTrap, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(flags), uintptr(perm), 0, 0)
37+
if errno != 0 {
38+
return 0, errno
39+
}
40+
41+
return int(fd), nil
42+
}
43+
44+
func Fstatat(dirfd int, path string, stat *syscall.Stat_t, flags int) error {
45+
var p *byte
46+
p, err := syscall.BytePtrFromString(path)
47+
if err != nil {
48+
return err
49+
}
50+
51+
_, _, errno := syscall.Syscall6(fstatatTrap, uintptr(dirfd), uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(stat)), uintptr(flags), 0, 0)
52+
if errno != 0 {
53+
return errno
54+
}
55+
56+
return nil
57+
58+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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 unix
6+
7+
const unlinkatTrap uintptr = 472
8+
const openatTrap uintptr = 463
9+
const fstatatTrap uintptr = 470
10+
11+
const AT_REMOVEDIR = 0x80
12+
const AT_SYMLINK_NOFOLLOW = 0x0020
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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 unix
6+
7+
import "syscall"
8+
9+
const unlinkatTrap uintptr = syscall.SYS_UNLINKAT
10+
const openatTrap uintptr = syscall.SYS_OPENAT
11+
const fstatatTrap uintptr = syscall.SYS_FSTATAT
12+
13+
const AT_REMOVEDIR = 0x2
14+
const AT_SYMLINK_NOFOLLOW = 0x1
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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 unix
6+
7+
import "syscall"
8+
9+
const unlinkatTrap uintptr = syscall.SYS_UNLINKAT
10+
const openatTrap uintptr = syscall.SYS_OPENAT
11+
const fstatatTrap uintptr = syscall.SYS_FSTATAT
12+
13+
const AT_REMOVEDIR = 0x800
14+
const AT_SYMLINK_NOFOLLOW = 0x200
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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 arm mips mipsle 386
6+
7+
package unix
8+
9+
import "syscall"
10+
11+
const fstatatTrap uintptr = syscall.SYS_FSTATAT64
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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 arm64
6+
7+
package unix
8+
9+
import "syscall"
10+
11+
const fstatatTrap uintptr = syscall.SYS_FSTATAT
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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 unix
6+
7+
import "syscall"
8+
9+
const unlinkatTrap uintptr = syscall.SYS_UNLINKAT
10+
const openatTrap uintptr = syscall.SYS_OPENAT
11+
12+
const AT_REMOVEDIR = 0x200
13+
const AT_SYMLINK_NOFOLLOW = 0x100
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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 unix
6+
7+
import "syscall"
8+
9+
const unlinkatTrap uintptr = syscall.SYS_UNLINKAT
10+
const openatTrap uintptr = syscall.SYS_OPENAT
11+
const fstatatTrap uintptr = syscall.SYS_FSTATAT
12+
13+
const AT_REMOVEDIR = 0x800
14+
const AT_SYMLINK_NOFOLLOW = 0x200
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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 amd64 mips64 mips64le ppc64 ppc64le s390x
6+
7+
package unix
8+
9+
import "syscall"
10+
11+
const fstatatTrap uintptr = syscall.SYS_NEWFSTATAT
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
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 unix
6+
7+
import "syscall"
8+
9+
const unlinkatTrap uintptr = syscall.SYS_UNLINKAT
10+
const openatTrap uintptr = syscall.SYS_OPENAT
11+
const fstatatTrap uintptr = syscall.SYS_FSTATAT
12+
13+
const AT_REMOVEDIR = 0x08
14+
const AT_SYMLINK_NOFOLLOW = 0x02

src/os/path.go

Lines changed: 0 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package os
66

77
import (
8-
"io"
98
"syscall"
109
)
1110

@@ -58,101 +57,3 @@ func MkdirAll(path string, perm FileMode) error {
5857
}
5958
return nil
6059
}
61-
62-
// RemoveAll removes path and any children it contains.
63-
// It removes everything it can but returns the first error
64-
// it encounters. If the path does not exist, RemoveAll
65-
// returns nil (no error).
66-
func RemoveAll(path string) error {
67-
// Simple case: if Remove works, we're done.
68-
err := Remove(path)
69-
if err == nil || IsNotExist(err) {
70-
return nil
71-
}
72-
73-
// Otherwise, is this a directory we need to recurse into?
74-
dir, serr := Lstat(path)
75-
if serr != nil {
76-
if serr, ok := serr.(*PathError); ok && (IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) {
77-
return nil
78-
}
79-
return serr
80-
}
81-
if !dir.IsDir() {
82-
// Not a directory; return the error from Remove.
83-
return err
84-
}
85-
86-
// Remove contents & return first error.
87-
err = nil
88-
for {
89-
fd, err := Open(path)
90-
if err != nil {
91-
if IsNotExist(err) {
92-
// Already deleted by someone else.
93-
return nil
94-
}
95-
return err
96-
}
97-
98-
const request = 1024
99-
names, err1 := fd.Readdirnames(request)
100-
101-
// Removing files from the directory may have caused
102-
// the OS to reshuffle it. Simply calling Readdirnames
103-
// again may skip some entries. The only reliable way
104-
// to avoid this is to close and re-open the
105-
// directory. See issue 20841.
106-
fd.Close()
107-
108-
for _, name := range names {
109-
err1 := RemoveAll(path + string(PathSeparator) + name)
110-
if err == nil {
111-
err = err1
112-
}
113-
}
114-
115-
if err1 == io.EOF {
116-
break
117-
}
118-
// If Readdirnames returned an error, use it.
119-
if err == nil {
120-
err = err1
121-
}
122-
if len(names) == 0 {
123-
break
124-
}
125-
126-
// We don't want to re-open unnecessarily, so if we
127-
// got fewer than request names from Readdirnames, try
128-
// simply removing the directory now. If that
129-
// succeeds, we are done.
130-
if len(names) < request {
131-
err1 := Remove(path)
132-
if err1 == nil || IsNotExist(err1) {
133-
return nil
134-
}
135-
136-
if err != nil {
137-
// We got some error removing the
138-
// directory contents, and since we
139-
// read fewer names than we requested
140-
// there probably aren't more files to
141-
// remove. Don't loop around to read
142-
// the directory again. We'll probably
143-
// just get the same error.
144-
return err
145-
}
146-
}
147-
}
148-
149-
// Remove directory.
150-
err1 := Remove(path)
151-
if err1 == nil || IsNotExist(err1) {
152-
return nil
153-
}
154-
if err == nil {
155-
err = err1
156-
}
157-
return err
158-
}

0 commit comments

Comments
 (0)