Skip to content

Commit

Permalink
os: add Root.Remove
Browse files Browse the repository at this point in the history
For #67002

Change-Id: Ibbf44c0bf62f53695a7399ba0dae5b84d5efd374
Reviewed-on: https://go-review.googlesource.com/c/go/+/627076
Reviewed-by: Quim Muntal <[email protected]>
Reviewed-by: Ian Lance Taylor <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
  • Loading branch information
neild committed Nov 20, 2024
1 parent 43d90c6 commit 49d24d4
Show file tree
Hide file tree
Showing 16 changed files with 331 additions and 4 deletions.
1 change: 1 addition & 0 deletions api/next/67002.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ pkg os, method (*Root) Name() string #67002
pkg os, method (*Root) Open(string) (*File, error) #67002
pkg os, method (*Root) OpenFile(string, int, fs.FileMode) (*File, error) #67002
pkg os, method (*Root) OpenRoot(string) (*Root, error) #67002
pkg os, method (*Root) Remove(string) error #67002
pkg os, type Root struct #67002
26 changes: 26 additions & 0 deletions src/internal/syscall/unix/at_wasip1.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,34 @@ const (
// to avoid changing AccessTime or ModifiedTime.
// Its value must match syscall/fs_wasip1.go
UTIME_OMIT = -0x2

AT_REMOVEDIR = 0x200
)

func Unlinkat(dirfd int, path string, flags int) error {
if flags&AT_REMOVEDIR == 0 {
return errnoErr(path_unlink_file(
int32(dirfd),
unsafe.StringData(path),
size(len(path)),
))
} else {
return errnoErr(path_remove_directory(
int32(dirfd),
unsafe.StringData(path),
size(len(path)),
))
}
}

//go:wasmimport wasi_snapshot_preview1 path_unlink_file
//go:noescape
func path_unlink_file(fd int32, path *byte, pathLen size) syscall.Errno

//go:wasmimport wasi_snapshot_preview1 path_remove_directory
//go:noescape
func path_remove_directory(fd int32, path *byte, pathLen size) syscall.Errno

func Openat(dirfd int, path string, flags int, perm uint32) (int, error) {
return syscall.Openat(dirfd, path, flags, perm)
}
Expand Down
70 changes: 70 additions & 0 deletions src/internal/syscall/windows/at_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package windows

import (
"syscall"
"unsafe"
)

// Openat flags not supported by syscall.Open.
Expand Down Expand Up @@ -171,3 +172,72 @@ func Mkdirat(dirfd syscall.Handle, name string, mode uint32) error {
syscall.CloseHandle(h)
return nil
}

func Deleteat(dirfd syscall.Handle, name string) error {
objAttrs := &OBJECT_ATTRIBUTES{}
if err := objAttrs.init(dirfd, name); err != nil {
return err
}
var h syscall.Handle
err := NtOpenFile(
&h,
DELETE,
objAttrs,
&IO_STATUS_BLOCK{},
FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE,
FILE_OPEN_REPARSE_POINT|FILE_OPEN_FOR_BACKUP_INTENT,
)
if err != nil {
return ntCreateFileError(err, 0)
}
defer syscall.CloseHandle(h)

const (
FileDispositionInformation = 13
FileDispositionInformationEx = 64
)

// First, attempt to delete the file using POSIX semantics
// (which permit a file to be deleted while it is still open).
// This matches the behavior of DeleteFileW.
err = NtSetInformationFile(
h,
&IO_STATUS_BLOCK{},
uintptr(unsafe.Pointer(&FILE_DISPOSITION_INFORMATION_EX{
Flags: FILE_DISPOSITION_DELETE |
FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK |
FILE_DISPOSITION_POSIX_SEMANTICS |
// This differs from DeleteFileW, but matches os.Remove's
// behavior on Unix platforms of permitting deletion of
// read-only files.
FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE,
})),
uint32(unsafe.Sizeof(FILE_DISPOSITION_INFORMATION_EX{})),
FileDispositionInformationEx,
)
switch err {
case nil:
return nil
case STATUS_CANNOT_DELETE, STATUS_DIRECTORY_NOT_EMPTY:
return err.(NTStatus).Errno()
}

// If the prior deletion failed, the filesystem either doesn't support
// POSIX semantics (for example, FAT), or hasn't implemented
// FILE_DISPOSITION_INFORMATION_EX.
//
// Try again.
err = NtSetInformationFile(
h,
&IO_STATUS_BLOCK{},
uintptr(unsafe.Pointer(&FILE_DISPOSITION_INFORMATION{
DeleteFile: true,
})),
uint32(unsafe.Sizeof(FILE_DISPOSITION_INFORMATION{})),
FileDispositionInformation,
)
if st, ok := err.(NTStatus); ok {
return st.Errno()
}
return err
}
4 changes: 4 additions & 0 deletions src/internal/syscall/windows/syscall_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -523,10 +523,14 @@ func (s NTStatus) Error() string {
// If this list starts getting long, we should consider generating the full set.
const (
STATUS_FILE_IS_A_DIRECTORY NTStatus = 0xC00000BA
STATUS_DIRECTORY_NOT_EMPTY NTStatus = 0xC0000101
STATUS_NOT_A_DIRECTORY NTStatus = 0xC0000103
STATUS_CANNOT_DELETE NTStatus = 0xC0000121
STATUS_REPARSE_POINT_ENCOUNTERED NTStatus = 0xC000050B
)

// NT Native APIs
//sys NtCreateFile(handle *syscall.Handle, access uint32, oa *OBJECT_ATTRIBUTES, iosb *IO_STATUS_BLOCK, allocationSize *int64, attributes uint32, share uint32, disposition uint32, options uint32, eabuffer uintptr, ealength uint32) (ntstatus error) = ntdll.NtCreateFile
//sys NtOpenFile(handle *syscall.Handle, access uint32, oa *OBJECT_ATTRIBUTES, iosb *IO_STATUS_BLOCK, share uint32, options uint32) (ntstatus error) = ntdll.NtOpenFile
//sys rtlNtStatusToDosErrorNoTeb(ntstatus NTStatus) (ret syscall.Errno) = ntdll.RtlNtStatusToDosErrorNoTeb
//sys NtSetInformationFile(handle syscall.Handle, iosb *IO_STATUS_BLOCK, inBuffer uintptr, inBufferLen uint32, class uint32) (ntstatus error) = ntdll.NtSetInformationFile
20 changes: 20 additions & 0 deletions src/internal/syscall/windows/types_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,3 +196,23 @@ const (
FILE_OPEN_NO_RECALL = 0x00400000
FILE_OPEN_FOR_FREE_SPACE_QUERY = 0x00800000
)

// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-_file_disposition_information
type FILE_DISPOSITION_INFORMATION struct {
DeleteFile bool
}

// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-_file_disposition_information_ex
type FILE_DISPOSITION_INFORMATION_EX struct {
Flags uint32
}

// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-_file_disposition_information_ex
const (
FILE_DISPOSITION_DO_NOT_DELETE = 0x00000000
FILE_DISPOSITION_DELETE = 0x00000001
FILE_DISPOSITION_POSIX_SEMANTICS = 0x00000002
FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK = 0x00000004
FILE_DISPOSITION_ON_CLOSE = 0x00000008
FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE = 0x00000010
)
18 changes: 18 additions & 0 deletions src/internal/syscall/windows/zsyscall_windows.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions src/os/os_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3815,3 +3815,23 @@ func TestAppendDoesntOverwrite(t *testing.T) {
}
})
}

func TestRemoveReadOnlyFile(t *testing.T) {
testMaybeRooted(t, func(t *testing.T, r *Root) {
if err := WriteFile("file", []byte("1"), 0); err != nil {
t.Fatal(err)
}
var err error
if r == nil {
err = Remove("file")
} else {
err = r.Remove("file")
}
if err != nil {
t.Fatalf("Remove read-only file: %v", err)
}
if _, err := Stat("file"); !IsNotExist(err) {
t.Fatalf("Stat read-only file after removal: %v (want IsNotExist)", err)
}
})
}
6 changes: 6 additions & 0 deletions src/os/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ func (r *Root) Mkdir(name string, perm FileMode) error {
return rootMkdir(r, name, perm)
}

// Remove removes the named file or (empty) directory in the root.
// See [Remove] for more details.
func (r *Root) Remove(name string) error {
return rootRemove(r, name)
}

func (r *Root) logOpen(name string) {
if log := testlog.Logger(); log != nil {
// This won't be right if r's name has changed since it was opened,
Expand Down
21 changes: 21 additions & 0 deletions src/os/root_js.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,24 @@ import (
"syscall"
)

// checkPathEscapes reports whether name escapes the root.
//
// Due to the lack of openat, checkPathEscapes is subject to TOCTOU races
// when symlinks change during the resolution process.
func checkPathEscapes(r *Root, name string) error {
return checkPathEscapesInternal(r, name, false)
}

// checkPathEscapesLstat reports whether name escapes the root.
// It does not resolve symlinks in the final path component.
//
// Due to the lack of openat, checkPathEscapes is subject to TOCTOU races
// when symlinks change during the resolution process.
func checkPathEscapesLstat(r *Root, name string) error {
return checkPathEscapesInternal(r, name, true)
}

func checkPathEscapesInternal(r *Root, name string, lstat bool) error {
if r.root.closed.Load() {
return ErrClosed
}
Expand Down Expand Up @@ -44,6 +61,10 @@ func checkPathEscapes(r *Root, name string) error {
continue
}

if lstat && i == len(parts)-1 {
break
}

next := joinPath(base, parts[i])
fi, err := Lstat(next)
if err != nil {
Expand Down
10 changes: 10 additions & 0 deletions src/os/root_noopenat.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,13 @@ func rootMkdir(r *Root, name string, perm FileMode) error {
}
return nil
}

func rootRemove(r *Root, name string) error {
if err := checkPathEscapesLstat(r, name); err != nil {
return &PathError{Op: "removeat", Path: name, Err: err}
}
if err := Remove(joinPath(r.root.name, name)); err != nil {
return &PathError{Op: "removeat", Path: name, Err: underlyingError(err)}
}
return nil
}
10 changes: 10 additions & 0 deletions src/os/root_openat.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ func rootMkdir(r *Root, name string, perm FileMode) error {
return err
}

func rootRemove(r *Root, name string) error {
_, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
return struct{}{}, removeat(parent, name)
})
if err != nil {
return &PathError{Op: "removeat", Path: name, Err: err}
}
return err
}

// doInRoot performs an operation on a path in a Root.
//
// It opens the directory containing the final element of the path,
Expand Down
4 changes: 4 additions & 0 deletions src/os/root_plan9.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ func checkPathEscapes(r *Root, name string) error {
}
return nil
}

func checkPathEscapesLstat(r *Root, name string) error {
return checkPathEscapes(r, name)
}
Loading

0 comments on commit 49d24d4

Please sign in to comment.