os: add Root.Readlink

For #67002

Change-Id: I532a5ffc02c7457796540db54fa2f5ddad86e4b2
Reviewed-on: https://go-review.googlesource.com/c/go/+/658995
Auto-Submit: Damien Neil <dneil@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Damien Neil 2025-03-18 15:12:31 -07:00 committed by Gopher Robot
parent 1eb1579fba
commit cb0d767a10
7 changed files with 79 additions and 0 deletions

View File

@ -2,3 +2,4 @@ pkg os, method (*Root) Chmod(string, fs.FileMode) error #67002
pkg os, method (*Root) Chown(string, int, int) error #67002
pkg os, method (*Root) Chtimes(string, time.Time, time.Time) error #67002
pkg os, method (*Root) Lchown(string, int, int) error #67002
pkg os, method (*Root) Readlink(string) (string, error) #67002

View File

@ -4,3 +4,4 @@ The [os.Root] type supports the following additional methods:
* [os.Root.Chown]
* [os.Root.Chtimes]
* [os.Root.Lchown]
* [os.Root.Readlink]

View File

@ -193,6 +193,12 @@ func (r *Root) Lstat(name string) (FileInfo, error) {
return rootStat(r, name, true)
}
// Readlink returns the destination of the named symbolic link in the root.
// See [Readlink] for more details.
func (r *Root) Readlink(name string) (string, error) {
return rootReadlink(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,

View File

@ -155,3 +155,14 @@ func rootRemove(r *Root, name string) error {
}
return nil
}
func rootReadlink(r *Root, name string) (string, error) {
if err := checkPathEscapesLstat(r, name); err != nil {
return "", &PathError{Op: "readlinkat", Path: name, Err: err}
}
name, err := Readlink(joinPath(r.root.name, name))
if err != nil {
return "", &PathError{Op: "readlinkat", Path: name, Err: underlyingError(err)}
}
return name, nil
}

View File

@ -118,6 +118,16 @@ func rootMkdir(r *Root, name string, perm FileMode) error {
return nil
}
func rootReadlink(r *Root, name string) (string, error) {
target, err := doInRoot(r, name, func(parent sysfdType, name string) (string, error) {
return readlinkat(parent, name)
})
if err != nil {
return "", &PathError{Op: "readlinkat", Path: name, Err: err}
}
return target, nil
}
func rootRemove(r *Root, name string) error {
_, err := doInRoot(r, name, func(parent sysfdType, name string) (struct{}, error) {
return struct{}{}, removeat(parent, name)

View File

@ -664,6 +664,35 @@ func TestRootLstat(t *testing.T) {
}
}
func TestRootReadlink(t *testing.T) {
for _, test := range rootTestCases {
test.run(t, func(t *testing.T, target string, root *os.Root) {
const content = "content"
wantError := test.wantError
if test.ltarget != "" {
// Readlink will read the final link, rather than following it.
wantError = false
} else {
// Readlink fails on non-link targets.
wantError = true
}
got, err := root.Readlink(test.open)
if errEndsTest(t, err, wantError, "root.Readlink(%q)", test.open) {
return
}
want, err := os.Readlink(filepath.Join(root.Name(), test.ltarget))
if err != nil {
t.Fatalf("os.Readlink(%q) = %v, want success", test.ltarget, err)
}
if got != want {
t.Errorf("root.Readlink(%q) = %q, want %q", test.open, got, want)
}
})
}
}
// A rootConsistencyTest is a test case comparing os.Root behavior with
// the corresponding non-Root function.
//
@ -1063,6 +1092,18 @@ func TestRootConsistencyLstat(t *testing.T) {
}
}
func TestRootConsistencyReadlink(t *testing.T) {
for _, test := range rootConsistencyTestCases {
test.run(t, func(t *testing.T, path string, r *os.Root) (string, error) {
if r == nil {
return os.Readlink(path)
} else {
return r.Readlink(path)
}
})
}
}
func TestRootRenameAfterOpen(t *testing.T) {
switch runtime.GOOS {
case "windows":

View File

@ -314,3 +314,12 @@ func chtimesat(dirfd syscall.Handle, name string, atime time.Time, mtime time.Ti
}
return syscall.SetFileTime(h, nil, &a, &w)
}
func readlinkat(dirfd syscall.Handle, name string) (string, error) {
fd, err := openat(dirfd, name, windows.O_OPEN_REPARSE, 0)
if err != nil {
return "", err
}
defer syscall.CloseHandle(fd)
return readReparseLinkHandle(fd)
}