]> Cypherpunks.ru repositories - gostls13.git/commitdiff
os: document Readlink behavior for relative links
authorBryan C. Mills <bcmills@google.com>
Mon, 4 Dec 2023 20:32:01 +0000 (15:32 -0500)
committerGopher Robot <gobot@golang.org>
Thu, 7 Dec 2023 17:27:54 +0000 (17:27 +0000)
Also provide a runnable example to illustrate that behavior.

This should help users to avoid the common mistake of expecting
os.Readlink to return an absolute path.

Fixes #57766.

Change-Id: I8f60aa111ebda0cae985758615019aaf26d5cb41
Reviewed-on: https://go-review.googlesource.com/c/go/+/546995
Auto-Submit: Bryan Mills <bcmills@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Reviewed-by: Rob Pike <r@golang.org>
src/os/example_test.go
src/os/file.go
src/os/file_plan9.go
src/os/file_unix.go
src/os/file_windows.go

index e9657ed1fcfe42f6a035571c948e09bdb331868b..656232c472e0a465475cc33f494df587b0301354 100644 (file)
@@ -263,3 +263,57 @@ func ExampleMkdirAll() {
                log.Fatal(err)
        }
 }
+
+func ExampleReadlink() {
+       // First, we create a relative symlink to a file.
+       d, err := os.MkdirTemp("", "")
+       if err != nil {
+               log.Fatal(err)
+       }
+       defer os.RemoveAll(d)
+       targetPath := filepath.Join(d, "hello.txt")
+       if err := os.WriteFile(targetPath, []byte("Hello, Gophers!"), 0644); err != nil {
+               log.Fatal(err)
+       }
+       linkPath := filepath.Join(d, "hello.link")
+       if err := os.Symlink("hello.txt", filepath.Join(d, "hello.link")); err != nil {
+               if errors.Is(err, errors.ErrUnsupported) {
+                       // Allow the example to run on platforms that do not support symbolic links.
+                       fmt.Printf("%s links to %s\n", filepath.Base(linkPath), "hello.txt")
+                       return
+               }
+               log.Fatal(err)
+       }
+
+       // Readlink returns the relative path as passed to os.Symlink.
+       dst, err := os.Readlink(linkPath)
+       if err != nil {
+               log.Fatal(err)
+       }
+       fmt.Printf("%s links to %s\n", filepath.Base(linkPath), dst)
+
+       var dstAbs string
+       if filepath.IsAbs(dst) {
+               dstAbs = dst
+       } else {
+               // Symlink targets are relative to the directory containing the link.
+               dstAbs = filepath.Join(filepath.Dir(linkPath), dst)
+       }
+
+       // Check that the target is correct by comparing it with os.Stat
+       // on the original target path.
+       dstInfo, err := os.Stat(dstAbs)
+       if err != nil {
+               log.Fatal(err)
+       }
+       targetInfo, err := os.Stat(targetPath)
+       if err != nil {
+               log.Fatal(err)
+       }
+       if !os.SameFile(dstInfo, targetInfo) {
+               log.Fatalf("link destination (%s) is not the same file as %s", dstAbs, targetPath)
+       }
+
+       // Output:
+       // hello.link links to hello.txt
+}
index 6fd0550eeb06b855f5d90b8a3d8e049c45211a01..090ffba4dc738c2b482053befbf896485df0acc3 100644 (file)
@@ -392,6 +392,15 @@ func Rename(oldpath, newpath string) error {
        return rename(oldpath, newpath)
 }
 
+// Readlink returns the destination of the named symbolic link.
+// If there is an error, it will be of type *PathError.
+//
+// If the link destination is relative, Readlink returns the relative path
+// without resolving it to an absolute one.
+func Readlink(name string) (string, error) {
+       return readlink(name)
+}
+
 // Many functions in package syscall return a count of -1 instead of 0.
 // Using fixCount(call()) instead of call() corrects the count.
 func fixCount(n int, err error) (int, error) {
index 03cdb5be4acbe0b7e2c65a675f91cd3f03dc8818..4cab2d4cdf6c8bc00cf2348f536c0ee19f5e686c 100644 (file)
@@ -505,9 +505,7 @@ func Symlink(oldname, newname string) error {
        return &LinkError{"symlink", oldname, newname, syscall.EPLAN9}
 }
 
-// Readlink returns the destination of the named symbolic link.
-// If there is an error, it will be of type *PathError.
-func Readlink(name string) (string, error) {
+func readlink(name string) (string, error) {
        return "", &PathError{Op: "readlink", Path: name, Err: syscall.EPLAN9}
 }
 
index 533a48404b9d090e390823222f2566348e8478e0..a527b23e4fabcec8e24d6e8ac91190ce99d9f6a4 100644 (file)
@@ -426,9 +426,7 @@ func Symlink(oldname, newname string) error {
        return nil
 }
 
-// Readlink returns the destination of the named symbolic link.
-// If there is an error, it will be of type *PathError.
-func Readlink(name string) (string, error) {
+func readlink(name string) (string, error) {
        for len := 128; ; len *= 2 {
                b := make([]byte, len)
                var (
index 63d53a1df819ff36d5b2cab4c86bb44bd64ab623..8b04ed6e47e39a6e69299b0217c3474c100e756c 100644 (file)
@@ -406,7 +406,7 @@ func normaliseLinkPath(path string) (string, error) {
        return "", errors.New("GetFinalPathNameByHandle returned unexpected path: " + s)
 }
 
-func readlink(path string) (string, error) {
+func readReparseLink(path string) (string, error) {
        h, err := openSymlink(path)
        if err != nil {
                return "", err
@@ -438,10 +438,8 @@ func readlink(path string) (string, error) {
        }
 }
 
-// Readlink returns the destination of the named symbolic link.
-// If there is an error, it will be of type *PathError.
-func Readlink(name string) (string, error) {
-       s, err := readlink(fixLongPath(name))
+func readlink(name string) (string, error) {
+       s, err := readReparseLink(fixLongPath(name))
        if err != nil {
                return "", &PathError{Op: "readlink", Path: name, Err: err}
        }