1// Package exepath provides information on the path used to invoke the running
2// program.
3//
4// Relativity
5//
6// This package is distinct from other packages providing similar functionality
7// because the path it provides is not realpath'd. That is, if a binary was
8// executed via a symlink, the path expressed is still expressed in terms of
9// that symlink. This is useful if you wish to use relative paths.
10//
11// For example:
12//
13//   /here/foo/
14//     bin: symlink to /somewhere/else/bin
15//     data.txt
16//
17//   /somewhere/else/bin
18//     foo
19//
20//   /here/foo$ ./bin/foo
21//
22// Here Abs in this package will specify /here/foo/bin/foo, meaning that
23// Join(Dir(Abs), "..") leads to /here/foo, not /somewhere/else, allowing
24// easy access to data.txt.
25package exepath // import "gopkg.in/hlandau/svcutils.v1/exepath"
26
27import (
28	"os"
29	"os/exec"
30	"path/filepath"
31	"strings"
32)
33
34// Absolute path to EXE which was invoked. This is set at init()-time.
35//
36// This path is not realpath'd — see package documentation.
37var Abs string
38
39func getRawPath() string {
40	// "github.com/kardianos/osext".Executable looks nice, but may return the
41	// realpath of the path because this is how the kernel returns it as
42	// /proc/self/exe. This causes problems with layouts like
43	//
44	//   some-work-directory/
45	//     bin/ -> symlink to $GOPATH/bin
46	//     src/ -> symlink to $GOPATH/src
47	//     etc/
48	//       ... configuration files ...
49	//
50	// where bin/foo is executed from some-work-directory and expects to find files in etc/.
51	// Since -fork reexecutes with the exepath.Abs path, this prevents paths like
52	//   $BIN/../etc/foo.conf from working (where $BIN is the dir of the executable path).
53	//
54	// Okay, maybe this is a byzantine configuration. But still, this breaks my existing
55	// configuration, so I'm sticking with os.Args[0] for now, as -fork should be as seamless
56	// as possible to relying applications.
57
58	return os.Args[0]
59}
60
61func init() {
62	rawPath := getRawPath()
63
64	// If there are no separators in rawPath, we've presumably been invoked from the path
65	// and should qualify the path accordingly.
66	idx := strings.IndexFunc(rawPath, func(r rune) bool {
67		return r == '/' || r == filepath.Separator
68	})
69	if idx < 0 {
70		abs, err := exec.LookPath(rawPath)
71		if err != nil {
72			return
73		}
74
75		Abs = abs
76	} else {
77		abs, err := filepath.Abs(rawPath)
78		if err != nil {
79			return
80		}
81
82		Abs = abs
83	}
84
85	initProgramName()
86}
87