]> Cypherpunks repositories - gostls13.git/commitdiff
os: add Root.Readlink
authorDamien Neil <dneil@google.com>
Tue, 18 Mar 2025 22:12:31 +0000 (15:12 -0700)
committerGopher Robot <gobot@golang.org>
Wed, 19 Mar 2025 19:00:51 +0000 (12:00 -0700)
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>

api/next/67002.txt
doc/next/6-stdlib/99-minor/os/67002.md
src/os/root.go
src/os/root_noopenat.go
src/os/root_openat.go
src/os/root_test.go
src/os/root_windows.go

index f7c3530f5941bfa25e4652664f6c1b484cb2558b..0e570d4fa0eea192ee8e84cf5868c1d8c37ed04e 100644 (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
index 629ea55ac0cc6da15661ff258f5156535b3fe539..4d9f66c19c3517b878fbb1efc1e3ed8a4ab1c69a 100644 (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]
index 27bd871c176b37bea00073726d401f8fe3d7834e..453ee1a5e52c694a68ad896a97c035e65703b1af 100644 (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,
index 46dd79473921dbbe305c29f295cfa4a687448773..f0e1aa5131d78ca20af61e5ab26e008f402dabdc 100644 (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
+}
index e47004e8eac68c06cfc30f66f1eaa7c2560e2ed1..8c07784b5a3a81f9753303856ba7622034f518ad 100644 (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)
index 5f0e733fe1425e558dfce021b05d8b0069a28020..6c8c8924297f37a24aaadd76dafdd579795d46dc 100644 (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":
index c56946d0d5534e947a3bae3ead8db9e303f44b1c..81fc5c320c70c95cef118c186ffbff584b3aa08b 100644 (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)
+}