Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

os: Stat does not follow relative symlinks on windows #19870

Closed
tamird opened this issue Apr 6, 2017 · 7 comments
Closed

os: Stat does not follow relative symlinks on windows #19870

tamird opened this issue Apr 6, 2017 · 7 comments
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done. OS-Windows
Milestone

Comments

@tamird
Copy link
Contributor

tamird commented Apr 6, 2017

Please answer these questions before submitting your issue. Thanks!

What version of Go are you using (go version)?

go version go1.8 windows/amd64

What operating system and processor architecture are you using (go env)?

set GOARCH=amd64
set GOBIN=
set GOEXE=.exe
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOOS=windows
set GOPATH=C:\Users\cockroach\go
set GORACE=
set GOROOT=C:\Go
set GOTOOLDIR=C:\Go\pkg\tool\windows_amd64
set GCCGO=gccgo
set CC=gcc
set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0 -fdebug-prefix-map=C:\Users\COCKRO~1\AppData\Local\Temp\2\go-build850954751=/tmp/go-build -gno-record-gcc-switches
set CXX=g++
set CGO_ENABLED=1
set PKG_CONFIG=pkg-config
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2

What did you do?

I ran a modified version of os.TestSymLink which creates relative symlinks outside of the cwd:

package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"
)

func TestLink(t *testing.T) {
	for _, isTmpDir := range []bool{true, false} {
		t.Run(fmt.Sprintf("tempDir=%t", isTmpDir), func(t *testing.T) {
			from, to := "smlinktestfrom", "symlinktestto"
			if isTmpDir {
				tmpDir, err := ioutil.TempDir("", "logtest")
				if err != nil {
					t.Fatalf("could not create temporary directory: %s", err)
				}
				defer func() {
					if err := os.RemoveAll(tmpDir); err != nil {
						t.Fatalf("failed to clean up temp directory: %s", err)
					}
				}()

				from, to = filepath.Join(tmpDir, from), filepath.Join(tmpDir, to)
			}

			os.Remove(from) // Just in case.
			file, err := os.Create(to)
			if err != nil {
				t.Fatalf("open %q failed: %v", to, err)
			}
			defer os.Remove(to)
			if err = file.Close(); err != nil {
				t.Errorf("close %q failed: %v", to, err)
			}
			err = os.Symlink(filepath.Base(to), from)
			if err != nil {
				t.Fatalf("symlink %q, %q failed: %v", to, from, err)
			}
			defer os.Remove(from)
			tostat, err := os.Lstat(to)
			if err != nil {
				t.Fatalf("stat %q failed: %v", to, err)
			}
			if tostat.Mode()&os.ModeSymlink != 0 {
				t.Fatalf("stat %q claims to have found a symlink", to)
			}
			fromstat, err := os.Stat(from)
			if err != nil {
				t.Fatalf("stat %q failed: %v", from, err)
			}
			if !os.SameFile(tostat, fromstat) {
				t.Errorf("symlink %q, %q did not create symlink", to, from)
			}
			fromstat, err = os.Lstat(from)
			if err != nil {
				t.Fatalf("lstat %q failed: %v", from, err)
			}
			if fromstat.Mode()&os.ModeSymlink == 0 {
				t.Fatalf("symlink %q, %q did not create symlink", to, from)
			}
			fromstat, err = os.Stat(from)
			if err != nil {
				t.Fatalf("stat %q failed: %v", from, err)
			}
			if fromstat.Mode()&os.ModeSymlink != 0 {
				t.Fatalf("stat %q did not follow symlink", from)
			}
			s, err := os.Readlink(from)
			if err != nil {
				t.Fatalf("readlink %q failed: %v", from, err)
			}
			if s != filepath.Base(to) {
				t.Fatalf("after symlink %q != %q", s, filepath.Base(to))
			}
			file, err = os.Open(from)
			if err != nil {
				t.Fatalf("open %q failed: %v", from, err)
			}
			file.Close()
		})
	}
}

What did you expect to see?

The same result I see on darwin:

ok  	github.com/cockroachdb/cockroach/foo	0.007s

What did you see instead?

=== RUN   TestLink
=== RUN   TestLink/tempDir=true
=== RUN   TestLink/tempDir=false
--- FAIL: TestLink (0.00s)
    --- FAIL: TestLink/tempDir=true (0.00s)
        stat_test.go:52: stat "C:\\Users\\COCKRO~1\\AppData\\Local\\Temp\\2\\logtest096314683\\smlinktestfrom" failed: GetFileAttributesEx symlinktestto: The system cannot find the file specified.
    --- PASS: TestLink/tempDir=false (0.00s)
FAIL
exit status 1
FAIL    _/C_/Users/cockroach/AppData/Local/Temp/2/logtest207355951      0.036s
@hirochachacha
Copy link
Contributor

hirochachacha commented Apr 7, 2017

Oh my! I suspect almost all os.Readlink usage in the stdlib do the same mistake.
We should use something like following one instead.

func readlink(link string) (string, error) {
	to, err := os.Readlink(link)
	if err != nil {
		return "", err
	}
	if filepath.IsAbs(to) {
		return to, nil
	}
	return filepath.Join(filepath.Dir(link), to), nil
}

IIUC, relative symlinks are not relative to $CWD, but relative to link path.

EDITED:
My suspicion was wrong. filepath.EvalSymlinks do the right thing.

@tamird
Copy link
Contributor Author

tamird commented Apr 7, 2017

IIUC, relative symlinks are not relative to $CWD, but relative to link path.

Correct.

@alexbrainman
Copy link
Member

IIUC, relative symlinks are not relative to $CWD, but relative to link path.
Correct.

Double correct. :-)

@hirochachacha would you like to send a change? With new test ...
Thank you.

Alex

PS: simplified version of the test above https://play.golang.org/p/vrY_AAb47I

@bradfitz bradfitz added this to the Go1.9 milestone Apr 7, 2017
@bradfitz bradfitz added NeedsFix The path to resolution is known, but the work has not been done. OS-Windows labels Apr 7, 2017
@hirochachacha
Copy link
Contributor

Double correct. :-)

Yeah! it must be correct.

@hirochachacha would you like to send a change? With new test ...
Thank you.

Sure! I'll do few hours later.

@gopherbot
Copy link

CL https://golang.org/cl/39932 mentions this issue.

BramGruneir added a commit to BramGruneir/cockroach that referenced this issue Apr 10, 2017
On Windows, calling os.Stat() on a symlinked file can return a file not found error instead of following the link. This change normalizes the path by calling EvalSymlinks on it first. See golang/go#19870 (comment).

Furthermore, I've cleaned up the code significantly here to be more streamlined and clear.

Fixes cockroachdb#14546.
BramGruneir added a commit to BramGruneir/cockroach that referenced this issue Apr 10, 2017
On Windows, os.Stat() interprets evaluates symlinks relative to the current working directory rather than the symlink's directory. This change normalizes the path by calling EvalSymlinks on it first. See golang/go#19870 (comment).

Furthermore, I've cleaned up the code significantly here to be more streamlined and clear.

Fixes cockroachdb#14546.
@hirochachacha
Copy link
Contributor

@alexbrainman I think path/filepath.EvalSymlinks covers relative symlinks well.

https://github.com/golang/go/blob/master/src/path/filepath/path_test.go#L752-L834

IMO, there are no need to add extra tests. Sorry about misdirection.

BramGruneir added a commit to BramGruneir/cockroach that referenced this issue Apr 11, 2017
On Windows, os.Stat() interprets evaluates symlinks relative to the current working directory rather than the symlink's directory. This change normalizes the path by calling EvalSymlinks on it first. See golang/go#19870 (comment).

Furthermore, I've cleaned up the code significantly here to be more streamlined and clear.

Fixes cockroachdb#14546.
BramGruneir added a commit to BramGruneir/cockroach that referenced this issue Apr 11, 2017
On Windows, os.Stat() interprets evaluates symlinks relative to the current working directory rather than the symlink's directory. This change normalizes the path by calling EvalSymlinks on it first. See golang/go#19870 (comment).

Furthermore, I've cleaned up the code significantly here to be more streamlined and clear.

Fixes cockroachdb#14546.
BramGruneir added a commit to BramGruneir/cockroach that referenced this issue Apr 11, 2017
On Windows, os.Stat() interprets evaluates symlinks relative to the current working directory rather than the symlink's directory. This change normalizes the path by calling EvalSymlinks on it first. See golang/go#19870 (comment).

Furthermore, I've cleaned up the code significantly here to be more streamlined and clear.

Fixes cockroachdb#14546.
BramGruneir added a commit to BramGruneir/cockroach that referenced this issue Apr 11, 2017
On Windows, os.Stat() interprets evaluates symlinks relative to the current working directory rather than the symlink's directory. This change normalizes the path by calling EvalSymlinks on it first. See golang/go#19870 (comment).

Furthermore, I've cleaned up the code significantly here to be more streamlined and clear.

Fixes cockroachdb#14546.
@alexbrainman
Copy link
Member

I think path/filepath.EvalSymlinks covers relative symlinks well.
...
IMO, there are no need to add extra tests

Sounds good. Thank you for looking.

Alex

lparth pushed a commit to lparth/go that referenced this issue Apr 13, 2017
Walk relative symlinks in windows os.Stat from
symlink path instead of from current directory.

Fixes golang#19870

Change-Id: I0a27473d11485f073084b1f19b30c5b3a2fbc0f7
Reviewed-on: https://go-review.googlesource.com/39932
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
@golang golang locked and limited conversation to collaborators Apr 12, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done. OS-Windows
Projects
None yet
Development

No branches or pull requests

5 participants