In Go, why does exec.Command() fail but os.StartProcess() succeed launching "winget.exe"?

huangapple go评论82阅读模式
英文:

In Go, why does exec.Command() fail but os.StartProcess() succeed launching "winget.exe"?

问题

exec.Command()可以用于执行C:\Windows\System32\notepad.exe,但无法执行C:\Users\<username>\AppData\Local\Microsoft\WindowsApps\winget.exe,并显示以下错误信息:exec: "C:\Users\<username>\AppData\Local\Microsoft\WindowsApps\winget.exe": file does not exist。然而,os.StartProcess()可以执行C:\Users\<username>\AppData\Local\Microsoft\WindowsApps\winget.exe

有人可以告诉我为什么吗?

这段代码无法正常工作,winget.exe没有被启动。

wingetPath := filepath.Join(os.Getenv("LOCALAPPDATA"),
	"Microsoft\\WindowsApps\\winget.exe")
cmd := exec.Command(wingetPath, "--version")
err := cmd.Start()
fmt.Println(err)
// exec: "C:\Users\<username>\AppData\Local\Microsoft\WindowsApps\winget.exe": file does not exist

但是这段代码可以正常工作:

wingetPath := filepath.Join(os.Getenv("LOCALAPPDATA"),
	"Microsoft\\WindowsApps\\winget.exe")
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, nil, nil}

// The argv slice will become os.Args in the new process,
// so it normally starts with the program name
_, err := os.StartProcess(wingetPath, []string{wingetPath, "--version"}, procAttr)
fmt.Println(err)
// <nil>

Go版本:

> go version
go version go1.18 windows/amd64
英文:
  • exec.Command() works for executing C:\Windows\System32\notepad.exe
  • But exec.Command() doesn't work for executing C:\Users\&lt;username&gt;\AppData\Local\Microsoft\WindowsApps\winget.exe. Fails with the error message:
    • exec: &quot;C:\\Users\\&lt;username&gt;\\AppData\\Local\\Microsoft\\WindowsApps\\winget.exe&quot;: file does not exist
  • However, os.StartProcess() works for executing C:\Users\&lt;username&gt;\AppData\Local\Microsoft\WindowsApps\winget.exe

Can someone tell me why?

This code fragment does not work. winget.exe isn't launched.

wingetPath := filepath.Join(os.Getenv(&quot;LOCALAPPDATA&quot;),
	&quot;Microsoft\\WindowsApps\\winget.exe&quot;)
cmd := exec.Command(wingetPath, &quot;--version&quot;)
err := cmd.Start()
fmt.Println(err)
// exec: &quot;C:\\Users\\&lt;username&gt;\\AppData\\Local\\Microsoft\\WindowsApps\\winget.exe&quot;: file does not exist

But this works:

wingetPath := filepath.Join(os.Getenv(&quot;LOCALAPPDATA&quot;),
	&quot;Microsoft\\WindowsApps\\winget.exe&quot;)
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, nil, nil}

// The argv slice will become os.Args in the new process,
// so it normally starts with the program name
_, err := os.StartProcess(wingetPath, []string{wingetPath, &quot;--version&quot;}, procAttr)
fmt.Println(err)
// &lt;nil&gt;

Go version:

&gt; go version
go version go1.18 windows/amd64

答案1

得分: 6

Golang中的Bug

显然,这是Go在Windows实现中的一个bug,并且已经在GitHub上多次提交了 - 我找到的最早的问题是几年前提交的这个问题

这个bug是由于exec.Command()在内部使用了os.Stat(),而os.Stat()无法正确读取带有重解析点的文件所导致的。而os.Lstat()可以。

Windows Store应用程序使用应用程序执行别名,它们实际上是带有重解析点的零字节文件。这个文章有一些额外的细节。

解决方法

  • 解决方法是使用os.StartProcess() - 一个较低级别的API,使用起来可能有些麻烦,特别是与os.Exec()相比。
    <br />重要提示:在os.StartProcess()中,argv切片将成为新进程中的os.Args,因此通常应将程序名称作为第一个参数传递:
wingetPath := filepath.Join(os.Getenv("LOCALAPPDATA"),
    "Microsoft\\WindowsApps\\winget.exe")
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, nil, nil}
/*
要重定向IO,请根据需要传递stdin、stdout、stderr
procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr}
*/

args := []string{"install", "git.git"}

// argv切片将成为新进程中的os.Args,因此通常以程序名称开头
proc, err := os.StartProcess(wingetPath,
   append([]string{wingetPath}, args...), procAttr)
fmt.Println(err) // nil
  • 另一种解决这个bug的方法是(创建并)执行一个.cmd文件(例如),该文件将(正确解析并)执行带有重解析点的文件。参见这个(以及这个目录)的示例。
英文:

Bug in Golang

So apparently this is a bug in the Windows implementation of Go and has been filed on GitHub many times - the oldest I could find is this issue which was filed years ago.

The bug is caused by the fact that exec.Command() internally uses os.Stat() which does not read files with reparse points correctly. os.Lstat() can.

Windows Store apps use App Execution Aliases, which are essentially zero-byte files with reparse points. This post has some additional details.

Workarounds

  • Workaround is to use os.StartProces() - a lower level API which can be a bit painful to use especially when compared to os.Exec().
    <br />Important: In os.StartProcess(), the argv slice will become os.Args in the new process, so you should normally pass the program name as the first argument:
wingetPath := filepath.Join(os.Getenv(&quot;LOCALAPPDATA&quot;),
    &quot;Microsoft\\WindowsApps\\winget.exe&quot;)
procAttr := new(os.ProcAttr)
procAttr.Files = []*os.File{nil, nil, nil}
/*
To redirect IO, pass in stdin, stdout, stderr as required
procAttr.Files = []*os.File{os.Stdin, os.Stdout, os.Stderr}
*/

args = []string { &quot;install&quot;, &quot;git.git&quot; }

// The argv slice will become os.Args in the new process,
// so it normally starts with the program name
proc, err := os.StartProcess(wingetPath,
   append([]string{wingetPath}, arg...), procAttr)
fmt.Println(err) // nil
  • Another approach to work around this bug is to (create and) execute a .cmd file (for example) which would (correctly resolve and) execute the file with reparse points. See this (and also this directory) for an example.

huangapple
  • 本文由 发表于 2022年4月14日 08:59:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/71865309.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定