aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBryan C. Mills <bcmills@google.com>2023-12-04 15:32:01 -0500
committerGopher Robot <gobot@golang.org>2023-12-07 17:27:54 +0000
commitbb34112d4df7b5dfd12fc83b8d1305631a7b8708 (patch)
treedf6e817d1136455672b11f491019bd728459d260 /src
parent4601857c1c7f5f72c75366763ff71fdcbe5f84be (diff)
downloadgo-bb34112d4df7b5dfd12fc83b8d1305631a7b8708.tar.xz
os: document Readlink behavior for relative links
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>
Diffstat (limited to 'src')
-rw-r--r--src/os/example_test.go54
-rw-r--r--src/os/file.go9
-rw-r--r--src/os/file_plan9.go4
-rw-r--r--src/os/file_unix.go4
-rw-r--r--src/os/file_windows.go8
5 files changed, 68 insertions, 11 deletions
diff --git a/src/os/example_test.go b/src/os/example_test.go
index e9657ed1fc..656232c472 100644
--- a/src/os/example_test.go
+++ b/src/os/example_test.go
@@ -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
+}
diff --git a/src/os/file.go b/src/os/file.go
index 6fd0550eeb..090ffba4dc 100644
--- a/src/os/file.go
+++ b/src/os/file.go
@@ -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) {
diff --git a/src/os/file_plan9.go b/src/os/file_plan9.go
index 03cdb5be4a..4cab2d4cdf 100644
--- a/src/os/file_plan9.go
+++ b/src/os/file_plan9.go
@@ -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}
}
diff --git a/src/os/file_unix.go b/src/os/file_unix.go
index 533a48404b..a527b23e4f 100644
--- a/src/os/file_unix.go
+++ b/src/os/file_unix.go
@@ -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 (
diff --git a/src/os/file_windows.go b/src/os/file_windows.go
index 63d53a1df8..8b04ed6e47 100644
--- a/src/os/file_windows.go
+++ b/src/os/file_windows.go
@@ -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}
}