blob: 0a448a561de267e6295536357675514d913cfb7e [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package lookpath implements utilities to find executables.
package lookpath
// TODO(toddw): implement for non-unix systems.
import (
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
)
func splitPath(env map[string]string) []string {
var dirs []string
for _, dir := range strings.Split(env["PATH"], string(filepath.ListSeparator)) {
if dir != "" {
dirs = append(dirs, dir)
}
}
return dirs
}
func isExecutable(info os.FileInfo) bool {
mode := info.Mode()
return !mode.IsDir() && mode&0111 != 0
}
// Look returns the absolute path of the executable with the given name. If
// name only contains a single path component, the dirs in env["PATH"] are
// consulted, and the first match is returned. Otherwise, for multi-component
// paths, the absolute path of the name is looked up directly.
//
// The behavior is the same as LookPath in the os/exec package, but allows the
// env to be passed in explicitly.
func Look(env map[string]string, name string) (string, error) {
var dirs []string
base := filepath.Base(name)
if base == name {
dirs = splitPath(env)
} else {
dirs = []string{filepath.Dir(name)}
}
for _, dir := range dirs {
file, err := filepath.Abs(filepath.Join(dir, base))
if err != nil {
continue
}
info, err := os.Stat(file)
if err != nil {
continue
}
if !isExecutable(info) {
continue
}
return file, nil
}
return "", &exec.Error{Name: name, Err: exec.ErrNotFound}
}
// LookPrefix returns the absolute paths of all executables with the given name
// prefix. If prefix only contains a single path component, the directories in
// env["PATH"] are consulted. Otherwise, for multi-component prefixes, only the
// directory containing the prefix is consulted. If multiple executables with
// the same base name match the prefix in different directories, the first match
// is returned. Returns a list of paths sorted by base name.
//
// The names are filled in as the method runs, to ensure the first matching
// property. As a consequence, you may pass in a pre-populated names map to
// prevent matching those names. It is fine to pass in a nil names map.
func LookPrefix(env map[string]string, prefix string, names map[string]bool) ([]string, error) {
if names == nil {
names = make(map[string]bool)
}
var dirs []string
if filepath.Base(prefix) == prefix {
dirs = splitPath(env)
} else {
dirs = []string{filepath.Dir(prefix)}
}
var all []string
for _, dir := range dirs {
dir, err := filepath.Abs(dir)
if err != nil {
continue
}
infos, err := ioutil.ReadDir(dir)
if err != nil {
continue
}
for _, info := range infos {
if !isExecutable(info) {
continue
}
name := info.Name()
file := filepath.Join(dir, name)
index := strings.LastIndex(file, prefix)
if index == -1 || strings.ContainsRune(file[index+len(prefix):], filepath.Separator) {
continue
}
if names[name] {
continue
}
names[name] = true
all = append(all, file)
}
}
if len(all) > 0 {
sort.Sort(byBase(all))
return all, nil
}
return nil, &exec.Error{Name: prefix + "*", Err: exec.ErrNotFound}
}
type byBase []string
func (x byBase) Len() int { return len(x) }
func (x byBase) Less(i, j int) bool { return filepath.Base(x[i]) < filepath.Base(x[j]) }
func (x byBase) Swap(i, j int) { x[i], x[j] = x[j], x[i] }