121 lines
2.3 KiB
Go
121 lines
2.3 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
func main() {
|
|
if len(os.Args) < 3 {
|
|
fmt.Fprintf(os.Stderr, "usage: envdir dir child\n")
|
|
os.Exit(111)
|
|
}
|
|
|
|
dir := os.Args[1]
|
|
child := os.Args[2]
|
|
childArgs := os.Args[2:]
|
|
|
|
env, err := buildEnv(dir)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "envdir: %s\n", err)
|
|
os.Exit(111)
|
|
}
|
|
|
|
path, err := findExecutable(child)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "envdir: %s\n", err)
|
|
os.Exit(111)
|
|
}
|
|
|
|
err = syscall.Exec(path, childArgs, env)
|
|
fmt.Fprintf(os.Stderr, "envdir: exec: %s\n", err)
|
|
os.Exit(111)
|
|
}
|
|
|
|
func buildEnv(dir string) ([]string, error) {
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to read directory %s: %w", dir, err)
|
|
}
|
|
|
|
// Start with the current environment.
|
|
envMap := make(map[string]string)
|
|
unset := make(map[string]bool)
|
|
for _, e := range os.Environ() {
|
|
k, v, _ := strings.Cut(e, "=")
|
|
envMap[k] = v
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
name := entry.Name()
|
|
|
|
// Skip subdirectories.
|
|
if entry.IsDir() {
|
|
continue
|
|
}
|
|
|
|
// Skip names containing '='.
|
|
if strings.Contains(name, "=") {
|
|
continue
|
|
}
|
|
|
|
path := filepath.Join(dir, name)
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to read %s: %w", path, err)
|
|
}
|
|
|
|
// If the file is empty, unset the variable.
|
|
if len(data) == 0 {
|
|
delete(envMap, name)
|
|
unset[name] = true
|
|
continue
|
|
}
|
|
|
|
// Use only the first line.
|
|
if i := bytes.IndexByte(data, '\n'); i >= 0 {
|
|
data = data[:i]
|
|
}
|
|
|
|
// Replace NUL bytes with newlines.
|
|
data = bytes.ReplaceAll(data, []byte{0}, []byte{'\n'})
|
|
|
|
// Trim trailing spaces and tabs.
|
|
value := strings.TrimRight(string(data), " \t")
|
|
|
|
envMap[name] = value
|
|
delete(unset, name)
|
|
}
|
|
|
|
env := make([]string, 0, len(envMap))
|
|
for k, v := range envMap {
|
|
env = append(env, k+"="+v)
|
|
}
|
|
return env, nil
|
|
}
|
|
|
|
func findExecutable(name string) (string, error) {
|
|
// If name contains a slash, use it directly.
|
|
if strings.Contains(name, "/") {
|
|
return name, nil
|
|
}
|
|
|
|
// Search PATH.
|
|
pathEnv := os.Getenv("PATH")
|
|
for _, dir := range filepath.SplitList(pathEnv) {
|
|
path := filepath.Join(dir, name)
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if info.Mode().IsRegular() && info.Mode()&0111 != 0 {
|
|
return path, nil
|
|
}
|
|
}
|
|
return "", fmt.Errorf("command not found: %s", name)
|
|
}
|