1// Copyright 2017 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//go:build aix || openbsd
6
7package os
8
9// We query the working directory at init, to use it later to search for the
10// executable file
11// errWd will be checked later, if we need to use initWd
12var initWd, errWd = Getwd()
13
14func executable() (string, error) {
15	var exePath string
16	if len(Args) == 0 || Args[0] == "" {
17		return "", ErrNotExist
18	}
19	if IsPathSeparator(Args[0][0]) {
20		// Args[0] is an absolute path, so it is the executable.
21		// Note that we only need to worry about Unix paths here.
22		exePath = Args[0]
23	} else {
24		for i := 1; i < len(Args[0]); i++ {
25			if IsPathSeparator(Args[0][i]) {
26				// Args[0] is a relative path: prepend the
27				// initial working directory.
28				if errWd != nil {
29					return "", errWd
30				}
31				exePath = initWd + string(PathSeparator) + Args[0]
32				break
33			}
34		}
35	}
36	if exePath != "" {
37		if err := isExecutable(exePath); err != nil {
38			return "", err
39		}
40		return exePath, nil
41	}
42	// Search for executable in $PATH.
43	for _, dir := range splitPathList(Getenv("PATH")) {
44		if len(dir) == 0 {
45			dir = "."
46		}
47		if !IsPathSeparator(dir[0]) {
48			if errWd != nil {
49				return "", errWd
50			}
51			dir = initWd + string(PathSeparator) + dir
52		}
53		exePath = dir + string(PathSeparator) + Args[0]
54		switch isExecutable(exePath) {
55		case nil:
56			return exePath, nil
57		case ErrPermission:
58			return "", ErrPermission
59		}
60	}
61	return "", ErrNotExist
62}
63
64// isExecutable returns an error if a given file is not an executable.
65func isExecutable(path string) error {
66	stat, err := Stat(path)
67	if err != nil {
68		return err
69	}
70	mode := stat.Mode()
71	if !mode.IsRegular() {
72		return ErrPermission
73	}
74	if (mode & 0111) == 0 {
75		return ErrPermission
76	}
77	return nil
78}
79
80// splitPathList splits a path list.
81// This is based on genSplit from strings/strings.go
82func splitPathList(pathList string) []string {
83	if pathList == "" {
84		return nil
85	}
86	n := 1
87	for i := 0; i < len(pathList); i++ {
88		if pathList[i] == PathListSeparator {
89			n++
90		}
91	}
92	start := 0
93	a := make([]string, n)
94	na := 0
95	for i := 0; i+1 <= len(pathList) && na+1 < n; i++ {
96		if pathList[i] == PathListSeparator {
97			a[na] = pathList[start:i]
98			na++
99			start = i + 1
100		}
101	}
102	a[na] = pathList[start:]
103	return a[:na+1]
104}
105