Skip to content

Commit 2fc67e7

Browse files
minuxbradfitz
authored andcommitted
os: add Executable() (string, error)
// Executable returns the path name for the executable that started // the current process. There is no guarantee that the path is still // pointing to the correct executable. If a symlink was used to start // the process, depending on the operating system, the result might // be the symlink or the path it pointed to. If a stable result is // needed, path/filepath.EvalSymlinks might help. // // Executable returns an absolute path unless an error occurred. // // The main use case is finding resources located relative to an // executable. // // Executable is not supported on nacl or OpenBSD (unless procfs is // mounted.) func Executable() (string, error) { return executable() } Fixes #12773. Change-Id: I469738d905b12f0b633ea4d88954f8859227a88c Reviewed-on: https://go-review.googlesource.com/16551 Run-TryBot: Minux Ma <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 119c30e commit 2fc67e7

8 files changed

+281
-0
lines changed

src/os/executable.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package os
6+
7+
// Executable returns the path name for the executable that started
8+
// the current process. There is no guarantee that the path is still
9+
// pointing to the correct executable. If a symlink was used to start
10+
// the process, depending on the operating system, the result might
11+
// be the symlink or the path it pointed to. If a stable result is
12+
// needed, path/filepath.EvalSymlinks might help.
13+
//
14+
// Executable returns an absolute path unless an error occurred.
15+
//
16+
// The main use case is finding resources located relative to an
17+
// executable.
18+
//
19+
// Executable is not supported on nacl or OpenBSD (unless procfs is
20+
// mounted.)
21+
func Executable() (string, error) {
22+
return executable()
23+
}

src/os/executable_darwin.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package os
6+
7+
var executablePath string // set by ../runtime/os_darwin.go
8+
9+
var initCwd, initCwdErr = Getwd()
10+
11+
func executable() (string, error) {
12+
ep := executablePath
13+
if ep[0] != '/' {
14+
if initCwdErr != nil {
15+
return ep, initCwdErr
16+
}
17+
if len(ep) > 2 && ep[0:2] == "./" {
18+
// skip "./"
19+
ep = ep[2:]
20+
}
21+
ep = initCwd + "/" + ep
22+
}
23+
return ep, nil
24+
}

src/os/executable_freebsd.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package os
6+
7+
import (
8+
"syscall"
9+
"unsafe"
10+
)
11+
12+
func executable() (string, error) {
13+
mib := [4]int32{1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1}
14+
15+
n := uintptr(0)
16+
// get length
17+
_, _, err := syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, 0, uintptr(unsafe.Pointer(&n)), 0, 0)
18+
if err != 0 {
19+
return "", err
20+
}
21+
if n == 0 { // shouldn't happen
22+
return "", nil
23+
}
24+
buf := make([]byte, n)
25+
_, _, err = syscall.Syscall6(syscall.SYS___SYSCTL, uintptr(unsafe.Pointer(&mib[0])), 4, uintptr(unsafe.Pointer(&buf[0])), uintptr(unsafe.Pointer(&n)), 0, 0)
26+
if err != 0 {
27+
return "", err
28+
}
29+
if n == 0 { // shouldn't happen
30+
return "", nil
31+
}
32+
return string(buf[:n-1]), nil
33+
}

src/os/executable_plan9.go

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build plan9
6+
7+
package os
8+
9+
import "syscall"
10+
11+
func executable() (string, error) {
12+
fn := "/proc/" + itoa(Getpid()) + "/text"
13+
f, err := Open(fn)
14+
if err != nil {
15+
return "", err
16+
}
17+
defer f.Close()
18+
return syscall.Fd2path(int(f.Fd()))
19+
}

src/os/executable_procfs.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build linux netbsd openbsd dragonfly nacl
6+
7+
package os
8+
9+
import (
10+
"errors"
11+
"runtime"
12+
)
13+
14+
// We query the executable path at init time to avoid the problem of
15+
// readlink returns a path appended with " (deleted)" when the original
16+
// binary gets deleted.
17+
var executablePath, executablePathErr = func () (string, error) {
18+
var procfn string
19+
switch runtime.GOOS {
20+
default:
21+
return "", errors.New("Executable not implemented for " + runtime.GOOS)
22+
case "linux":
23+
procfn = "/proc/self/exe"
24+
case "netbsd":
25+
procfn = "/proc/curproc/exe"
26+
case "openbsd":
27+
procfn = "/proc/curproc/file"
28+
case "dragonfly":
29+
procfn = "/proc/curproc/file"
30+
}
31+
return Readlink(procfn)
32+
}()
33+
34+
func executable() (string, error) {
35+
return executablePath, executablePathErr
36+
}

src/os/executable_solaris.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package os
6+
7+
import "syscall"
8+
9+
var initCwd, initCwdErr = Getwd()
10+
11+
func executable() (string, error) {
12+
path, err := syscall.Getexecname()
13+
if err != nil {
14+
return path, err
15+
}
16+
if len(path) > 0 && path[0] != '/' {
17+
if initCwdErr != nil {
18+
return path, initCwdErr
19+
}
20+
if len(path) > 2 && path[0:2] == "./" {
21+
// skip "./"
22+
path = path[2:]
23+
}
24+
return initCwd + "/" + path, nil
25+
}
26+
return path, nil
27+
}

src/os/executable_test.go

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package os_test
6+
7+
import (
8+
"fmt"
9+
"internal/testenv"
10+
"os"
11+
osexec "os/exec"
12+
"path/filepath"
13+
"runtime"
14+
"testing"
15+
)
16+
17+
const executable_EnvVar = "OSTEST_OUTPUT_EXECPATH"
18+
19+
func TestExecutable(t *testing.T) {
20+
testenv.MustHaveExec(t) // will also execlude nacl, which doesn't support Executable anyway
21+
ep, err := os.Executable()
22+
if err != nil {
23+
switch goos := runtime.GOOS; goos {
24+
case "openbsd": // procfs is not mounted by default
25+
t.Skipf("Executable failed on %s: %v, expected", goos, err)
26+
}
27+
t.Fatalf("Executable failed: %v", err)
28+
}
29+
// we want fn to be of the form "dir/prog"
30+
dir := filepath.Dir(filepath.Dir(ep))
31+
fn, err := filepath.Rel(dir, ep)
32+
if err != nil {
33+
t.Fatalf("filepath.Rel: %v", err)
34+
}
35+
cmd := &osexec.Cmd{}
36+
// make child start with a relative program path
37+
cmd.Dir = dir
38+
cmd.Path = fn
39+
// forge argv[0] for child, so that we can verify we could correctly
40+
// get real path of the executable without influenced by argv[0].
41+
cmd.Args = []string{"-", "-test.run=XXXX"}
42+
cmd.Env = append(os.Environ(), fmt.Sprintf("%s=1", executable_EnvVar))
43+
out, err := cmd.CombinedOutput()
44+
if err != nil {
45+
t.Fatalf("exec(self) failed: %v", err)
46+
}
47+
outs := string(out)
48+
if !filepath.IsAbs(outs) {
49+
t.Fatalf("Child returned %q, want an absolute path", out)
50+
}
51+
if !sameFile(outs, ep) {
52+
t.Fatalf("Child returned %q, not the same file as %q", out, ep)
53+
}
54+
}
55+
56+
func sameFile(fn1, fn2 string) bool {
57+
fi1, err := os.Stat(fn1)
58+
if err != nil {
59+
return false
60+
}
61+
fi2, err := os.Stat(fn2)
62+
if err != nil {
63+
return false
64+
}
65+
return os.SameFile(fi1, fi2)
66+
}
67+
68+
func init() {
69+
if e := os.Getenv(executable_EnvVar); e != "" {
70+
// first chdir to another path
71+
dir := "/"
72+
if runtime.GOOS == "windows" {
73+
cwd, err := os.Getwd()
74+
if err != nil {
75+
panic(err)
76+
}
77+
dir = filepath.VolumeName(cwd)
78+
}
79+
os.Chdir(dir)
80+
if ep, err := os.Executable(); err != nil {
81+
fmt.Fprint(os.Stderr, "ERROR: ", err)
82+
} else {
83+
fmt.Fprint(os.Stderr, ep)
84+
}
85+
os.Exit(0)
86+
}
87+
}

src/os/executable_windows.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright 2016 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package os
6+
7+
import (
8+
"internal/syscall/windows"
9+
"syscall"
10+
)
11+
12+
func getModuleFileName(handle syscall.Handle) (string, error) {
13+
n := uint32(1024)
14+
var buf []uint16
15+
for {
16+
buf = make([]uint16, n)
17+
r, err := windows.GetModuleFileName(handle, &buf[0], n)
18+
if err != nil {
19+
return "", err
20+
}
21+
if r < n {
22+
break
23+
}
24+
// r == n means n not big enough
25+
n += 1024
26+
}
27+
return syscall.UTF16ToString(buf), nil
28+
}
29+
30+
func executable() (string, error) {
31+
return getModuleFileName(0)
32+
}

0 commit comments

Comments
 (0)