go: os: Symlink on Windows with a to-be-created directory path silently creates a link of the wrong type
(Discovered via debugging on #38772.)
What version of Go are you using (go version)?
gopher@SERVER-2016-V7- C:\workdir\go\src>..\bin\go version go version devel gomote.XXXXX windows/amd64
Does this issue reproduce with the latest release?
Yes.
What operating system and processor architecture are you using (go env)?
go env Output
gopher@SERVER-2016-V7- C:\workdir\go\src>..\bin\go version go version devel gomote.XXXXX windows/amd64gopher@SERVER-2016-V7- C:\workdir\go\src>…\bin\go env set GO111MODULE= set GOARCH=amd64 set GOBIN= set GOCACHE=C:\Users\gopher\AppData\Local\go-build set GOENV=C:\Users\gopher\AppData\Roaming\go\env set GOEXE=.exe set GOFLAGS= set GOHOSTARCH=amd64 set GOHOSTOS=windows set GOINSECURE= set GOMODCACHE=C:\Users\gopher\go\pkg\mod set GONOPROXY= set GONOSUMDB= set GOOS=windows set GOPATH=C:\Users\gopher\go set GOPRIVATE= set GOPROXY=https://proxy.golang.org,direct set GOROOT=C:\workdir\go set GOSUMDB=sum.golang.org set GOTMPDIR= set GOTOOLDIR=C:\workdir\go\pkg\tool\windows_amd64 set GCCGO=gccgo set GOAMD64=alignedjumps set AR=ar set CC=gcc set CXX=g++ set CGO_ENABLED=0 set GOMOD=C:\workdir\go\src\go.mod set CGO_CFLAGS=-g -O2 set CGO_CPPFLAGS= set CGO_CXXFLAGS=-g -O2 set CGO_FFLAGS=-g -O2 set CGO_LDFLAGS=-g -O2 set PKG_CONFIG=pkg-config set GOGCCFLAGS=-m64 -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map= C:\Users\gopher\AppData\Local\Temp\go-build141789646=/tmp/go-build -gno-record-gcc-switches
What did you do?
err := os.Symlink(to, from)
if err != nil {
t.Fatalf(…)
}
err = os.Mkdir(to, 0755)
if err != nil {
t.Fatalf(…)
}
…
file, err := os.Open(from)
if err != nil {
t.Fatalf(…)
}
file.Close()
CL 234737 contains a complete example.
What did you expect to see?
Either a non-nil error from the call to os.Symlink, or a successful call to os.Open.
What did you see instead?
gopher@SERVER-2016-V7- C:\workdir\go\src>..\bin\go test os -run=TestSymlink.*
--- FAIL: TestSymlinkBeforeTargetFileExists (0.00s)
os_test.go:908: Open("earlysymlinktestfrom") failed: open earlysymlinktestfrom: Access is denied
.
FAIL
FAIL os 0.052s
FAIL
Diagnosis
The Windows CreateSymbolicLinkA system call requires a flag to indicate whether the destination is a file or a directory. (If SYMBOLIC_LINK_FLAG_DIRECTORY is set, the symlink is a directory symlink; otherwise, it is a file symlink.)
The current implementation of os.Symlink on Windows uses a call to os.Stat to determine which kind of link to create. Unfortunately, if that Stat call fails, Symlink assumes that it is a file rather than reporting the error to the caller:
https://github.com/golang/go/blob/567556d78657326c99b8fa84ec2a5ee511a0941b/src/os/file_windows.go#L337-L338
That assumption seems like a mistake. If a Windows user needs to create a symlink to a not-yet-existing file or directory on Windows, they should be made aware that the call is missing essential information (the destination type), and can then make an explicit choice to use golang.org/x/sys/windows.CreateSymbolicLink instead of os.Syscall if they are able to supply the missing information.
I think we should change os.Symlink at the beginning of the Go 1.16 cycle so that it propagates the Stat error instead of implicitly assuming that the destination is a file.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 2
- Comments: 49 (29 by maintainers)
Commits related to this issue
- os: in Symlink, stat the correct target path for drive-relative targets on Windows Previously, when the target (“old”) path passed to os.Symlink was a “root-relative” Windows path,¹ we would erroneou... — committed to golang/go by bcmills 4 years ago
- go/packages/packagestest: make Export skip tests involving unsupported links Also make Export create all parent directories before all files. If the files are symlinks to directories, the target dire... — committed to golang/tools by bcmills 4 years ago
- go/packages/packagestest: make Export skip tests involving unsupported links Also make Export create all parent directories before all files. If the files are symlinks to directories, the target dire... — committed to rinchsan/gosimports by bcmills 4 years ago
Thanks for writing down all the cases. I agree that we should just document.
For people using Windows who need to create a symlink to a directory that does not yet exist, I think the approach is either 1) create the directory first; 2) use x/sys/windows.
I don’t think we need an
AlignSymlinkfunction. It should be reliable to remove the symlink and recreate it. Accessing the symlink won’t work while it is removed, but it won’t work before it is removed wither.I don’t have any creative solutions for this problem, unfortunately. We run into a similar issue when trying to determine what symlink type to create when creating symbolic links in WSL. If the target doesn’t exist, or if the target type changes after creating the symlink, then we get it wrong. This breaks symlink accesses from Windows (but not from WSL since we interpret the symlinks there, ignoring the type flag).
Ideally we would change Windows to not require this distinction, but so far we don’t think that’s practical.
@bcmills
But that is not what symlinks created with CreateSymbolicLink API do. For example, they could point to a file on another volume / drive, and that drive might not be mounted all the time. It is obviously fine for symlinks point to non-existing file / directory.
Did you read
https://docs.microsoft.com/en-us/windows/win32/fileio/hard-links-and-junctions
?
Alex