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

cmd/go: go build (etc.) doesn't work with symlinks when invoked via os/exec.Command with mismatched PWD #63644

Closed
adamroyjones opened this issue Oct 20, 2023 · 3 comments

Comments

@adamroyjones
Copy link

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

$ go version
go version go1.21.3 linux/amd64

Does this issue reproduce with the latest release?

Yes.

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

go env Output
$ go env
GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/root/.cache/go-build'
GOENV='/root/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='local'
GOTOOLDIR='/usr/local/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.21.3'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/tmp/reprex/runner/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build1307121550=/tmp/go-build -gno-record-gcc-switches'

What did you do?

docker container run -it golang:1.21.3-bookworm bash

followed by

mkdir /tmp/reprex
cd /tmp/reprex
mkdir orig
ln -s orig symlink
cd orig
go mod init orig

cat <<EOF > main.go
package main
import "fmt"
func main() { fmt.Println("Hello, world!") }
EOF

mkdir /tmp/reprex/runner
cd /tmp/reprex/runner
go mod init runner

cat <<EOF > main.go
package main
import (
  "os"
  "os/exec"
)
func main() {
  src := os.Args[1]
  cmd := exec.Command("go", "run", src)
  cmd.Env = os.Environ()
  cmd.Dir = src
  cmd.Stdout = os.Stdout
  cmd.Stderr = os.Stderr
  if err := cmd.Run(); err != nil {
    panic(err)
  }
}
EOF

go run . /tmp/reprex/orig # fine
go run . /tmp/reprex/symlink # not fine

What did you expect to see?

I would expect the last command to print out "Hello, world!".

What did you see instead?

directory ../symlink outside main module or its selected dependencies
panic: exit status 1

goroutine 1 [running]:
main.main()
	/tmp/reprex/runner/main.go:14 +0x165
exit status 2
@adamroyjones
Copy link
Author

Though maybe I'm missing something.

@adamroyjones adamroyjones changed the title cmd/go: go build (etc.) doesn't work with symlinks cmd/go: go build (etc.) doesn't work with symlinks when invoked via os/exec.Command Oct 20, 2023
@bcmills
Copy link
Contributor

bcmills commented Oct 23, 2023

This is as expected given #50599 (see the “Limitations” section of that proposal).

Because you have explicitly set cmd.Env to a non-nil slice, the PWD environment variable is not automatically updated to match cmd.Dir — instead, it retains the value that was set by the command cd /tmp/reprex/runner.

When the program invokes go run /tmp/reprex/symlink, the go command is run in the directory referred to by /tmp/reprex/symlink, but with a PWD that does not refer to that directory, so it cannot use the PWD variable to determine the working directory. It falls back to computing the working directory by walking the filesystem upward, which gives a path that does not involve symlinks (such as /tmp/reprex/orig). Because that directory contains a go.mod file, that is used as the location of the main module. You are requesting to run the program contained in /tmp/reprex/symlink, which is not lexically in the directory /tmp/reprex/orig, so it is considered to be outside of the main module.

The go command compares directories lexically because to do otherwise is not only much more expensive, but also potentially confusing — for example, it could cause error messages to refer to some directory other than the one you actually provided.

The fix is for your program to make use of #50599 to set PWD to correctly match the command's working directory (as required by POSIX):

  cmd := exec.Command("go", "run", src)
  cmd.Dir = src
  cmd.Env = cmd.Environ()  // Call after setting cmd.Dir to get the correct PWD.
  cmd.Stdout = os.Stdout
  cmd.Stderr = os.Stderr

@bcmills bcmills closed this as not planned Won't fix, can't repro, duplicate, stale Oct 23, 2023
@bcmills bcmills added the GoCommand cmd/go label Oct 23, 2023
@bcmills bcmills changed the title cmd/go: go build (etc.) doesn't work with symlinks when invoked via os/exec.Command cmd/go: go build (etc.) doesn't work with symlinks when invoked via os/exec.Command with mismatched PWD Oct 23, 2023
@adamroyjones
Copy link
Author

adamroyjones commented Oct 23, 2023

That's very illuminating—thank you for shining a light on it.

Hopefully anyone else who trips themselves up over this can find this thread.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants