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/exec: calling Command on file with UWP reparse point fails with "file does not exist" #42919

Open
viveklak opened this issue Dec 1, 2020 · 4 comments

Comments

@viveklak
Copy link

@viveklak viveklak commented Dec 1, 2020

Windows 10 ships with some 0 length files in C:\Users\<User>\AppData\Local\Microsoft\WindowsApps with special reparse points which when run trigger the windows app store app to be launched.

Once installed, the same files result in the installed app to be run going forward. For instance, Windows 10 ships with python3.exe which is in fact a link to python3.8 available on the Windows App Store. C:\Users\<User>\AppData\Local\Microsoft\WindowsApps happens to be on the PATH by default so once installed, the python3 interpreter should be invoked. However, when trying to run the same through Go's exec.Cmd we get a file does not exist error (see below for simple repro).

The main issue seems to be a call to os.Stat from the exec.Cmd run path. os.Stat on windows doesn't pass syscall.FILE_FLAG_OPEN_REPARSE_POINT resulting in the error (unlike Lstat).

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

$ go version
1.15.5

Does this issue reproduce with the latest release?

Yes

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

Windows 10

go env Output
$ go env

set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\User\AppData\Local\go-build
set GOENV=C:\Users\User\AppData\Roaming\go\env
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=C:\Users\User\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\User\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=c:\go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=c:\go\pkg\tool\windows_amd64
set GCCGO=gccgo
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=
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 -mthreads -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=C:\Users\User\AppData\Local\Temp\go-build056150306=/tmp/go-build -gno-record-gcc-switches

What did you do?

$ cat main.go
package main

import (
	"fmt"
	"os"
	"os/exec"
)

func do() error {
	res, err := os.Lstat("C:\\Users\\User\\AppData\\Local\\Microsoft\\WindowsApps\\python3.exe")
	if err != nil {
		return err
	}
	fmt.Println("python3 exists!")
	fmt.Printf("%#v\n", res)
	cmd := exec.Command("C:\\Users\\User\\AppData\\Local\\Microsoft\\WindowsApps\\python3.exe", "-c", "print('hello')")
	btes, err := cmd.CombinedOutput()
	if err != nil {
		return err
	}
	fmt.Printf("%v\n", btes)
	return nil
}

func main() {
	err := do()
	if err != nil {
		panic(err)
	}
}

PS C:\Users\User> go run main.go

What did you expect to see?

python3 exists!
&os.fileStat{name:"python3.exe", FileAttributes:0x420, CreationTime:syscall.Filetime{LowDateTime:0x5094a4bc, HighDateTime:0x1d6a0b0}, LastAccessTime:syscall.Filetime{LowDateTime:0xb3aada47, HighDateTime:0x1d6be0b}, LastWriteTime:syscall.Filetime{LowDateTime:0xb3aada47, HighDateTime:0x1d6be0b}, FileSizeHigh:0x0, FileSizeLow:0x0, Reserved0:0x8000001b, filetype:0x0, Mutex:sync.Mutex{state:0, sema:0x0}, path:"", vol:0x16999dcb, idxhi:0x30000, idxlo:0x14bc9, appendNameToPath:false}
hello

What did you see instead?

python3 exists!
&os.fileStat{name:"python3.exe", FileAttributes:0x420, CreationTime:syscall.Filetime{LowDateTime:0x5094a4bc, HighDateTime:0x1d6a0b0}, LastAccessTime:syscall.Filetime{LowDateTime:0xb3aada47, HighDateTime:0x1d6be0b}, LastWriteTime:syscall.Filetime{LowDateTime:0xb3aada47, HighDateTime:0x1d6be0b}, FileSizeHigh:0x0, FileSizeLow:0x0, Reserved0:0x8000001b, filetype:0x0, Mutex:sync.Mutex{state:0, sema:0x0}, path:"", vol:0x16999dcb, idxhi:0x30000, idxlo:0x14bc9, appendNameToPath:false}
panic: exec: "C:\\Users\\User\\AppData\\Local\\Microsoft\\WindowsApps\\python3.exe": file does not exist

goroutine 1 [running]:
main.main()
        C:/Users/User/main.go:28 +0x4b
exit status 2
@viveklak viveklak changed the title exec.Command on file with UWP reparse point links fails with "file does not exist" exec.Command on file with UWP reparse point fails with "file does not exist" Dec 1, 2020
@networkimprov
Copy link

@networkimprov networkimprov commented Dec 5, 2020

cc @alexbrainman @mattn

@gopherbot add OS-Windows

Loading

viveklak added a commit to pulumi/pulumi that referenced this issue Dec 7, 2020
viveklak added a commit to pulumi/pulumi that referenced this issue Dec 7, 2020
viveklak added a commit to pulumi/pulumi that referenced this issue Dec 7, 2020
@cagedmantis cagedmantis changed the title exec.Command on file with UWP reparse point fails with "file does not exist" os/exec: calling Command on file with UWP reparse point fails with "file does not exist" Dec 8, 2020
@cagedmantis cagedmantis added this to the Backlog milestone Dec 8, 2020
@cagedmantis
Copy link
Contributor

@cagedmantis cagedmantis commented Dec 8, 2020

Loading

@mattn
Copy link
Member

@mattn mattn commented Dec 8, 2020

It caused by that findExecutable uses os.Stat instead of os.Lstat. os.Lstat resolve reparse-points.

diff --git a/src/os/exec/lp_windows.go b/src/os/exec/lp_windows.go
index 9ea3d76575..12c7e04e63 100644
--- a/src/os/exec/lp_windows.go
+++ b/src/os/exec/lp_windows.go
@@ -15,7 +15,7 @@ import (
 var ErrNotFound = errors.New("executable file not found in %PATH%")
 
 func chkStat(file string) error {
-	d, err := os.Stat(file)
+	d, err := os.Lstat(file)
 	if err != nil {
 		return err
 	}

This fixes the issue the program can not execute python.exe, but the python.exe exit with 9009 exit code. Probably, it is another issue.

Loading

@viveklak
Copy link
Author

@viveklak viveklak commented Dec 8, 2020

This fixes the issue the program can not execute python.exe, but the python.exe exit with 9009 exit code. Probably, it is another issue.

Yes - I alluded to that in the original report but should have called it out better:

The main issue seems to be a call to os.Stat from the exec.Cmd run path. os.Stat on windows doesn't pass syscall.FILE_FLAG_OPEN_REPARSE_POINT resulting in the error (unlike Lstat).

A naive thought here would be to consider passing syscall.FILE_FLAG_OPEN_REPARSE_POINT to CreateFile from os.Stat similar to what os.Lstat does but I am not familiar enough with the semantics of Windows to judge the implications of that change.

As such, I see the following issues:

  1. Running os.Stat on reparse point files returns an unexpected/misleading error
  2. os.Exec is not able to exec a reparse point file due to the above.

Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
5 participants