forgekey: a command starts in a directory that exists (issue 734) #93

Merged
mesh-tools merged 2 commits from fix/daemon-dir-kept into main 2026-10-26 14:15:57 +00:00
2 changed files with 43 additions and 2 deletions
+26 -1
View File
@@ -43,7 +43,8 @@ func ExecRunnerFor(bound time.Duration) Runner {
// ExecRunnerClean is ExecRunnerFor whose commands get exactly the env argument as their environment, and nothing of
// this process's (issue 1497; novox/hq ADR 0466 §4: no registry password in a fetch's environment). The caller
// gives PATH and HOME itself. A directory is the command's own business (`git -C`, `go -C`).
// gives PATH and HOME itself. A directory is the command's own business (`git -C`, `go -C`): it is started in the HOME
// it is given, or the machine's temporary directory, never in this process's (stableDir).
func ExecRunnerClean(bound time.Duration) Runner {
return func(ctx context.Context, env []string, name string, args ...string) Ran {
return execRun(ctx, bound, true, env, name, args...)
@@ -62,6 +63,7 @@ func execRun(ctx context.Context, bound time.Duration, clean bool, env []string,
} else {
cmd.Env = append(os.Environ(), env...)
}
cmd.Dir = stableDir(clean, env)
var out, errb bytes.Buffer
cmd.Stdout, cmd.Stderr = &out, &errb
err := cmd.Run()
@@ -83,6 +85,29 @@ func execRun(ctx context.Context, bound time.Duration, clean bool, env []string,
return r
}
// stableDir is the directory a command starts in when it names none (novox/hq issue 734). **Never one that may be
// deleted under it**: a module runs in the directory its build was unpacked in, which a new build replaces, and a
// command started there inherits it — `go version` then fails "getwd: no such file or directory" before it reads
// anything. A clean command starts in the HOME it is given, or the temporary directory; any other keeps this
// process's directory while it exists, and starts in the temporary directory once it is gone. "" is this process's.
func stableDir(clean bool, env []string) string {
if !clean {
if _, err := os.Getwd(); err == nil {
return ""
}
return os.TempDir()
}
for i := len(env) - 1; i >= 0; i-- {
if home, ok := strings.CutPrefix(env[i], "HOME="); ok {
if info, err := os.Stat(home); err == nil && info.IsDir() {
return home
}
break
}
}
return os.TempDir()
}
func firstLine(s string) string {
for _, l := range strings.Split(s, "\n") {
if l = strings.TrimSpace(l); l != "" {
+17 -1
View File
@@ -48,13 +48,29 @@ func TestExecRunnerForZeroIsCutByItsContext(t *testing.T) {
}
}
// 1497/AC4: ExecRunnerClean(0) runs env with only the given words: PATH and HOME, not the process's secret.
// 1497/AC4: ExecRunnerClean(0) runs env with only the given words: PATH and HOME, not the process's secret. And issue
// 734: run from a process whose own directory was deleted under it (a module whose build a new one replaced), a clean
// command starts in its HOME, and any other in the temporary directory, rather than failing to find where it is.
func TestExecRunnerCleanGivesTheCommandOnlyTheEnvArgument(t *testing.T) {
t.Setenv("MESH_TEST_SECRET", "x")
r := ExecRunnerClean(0)(context.Background(), []string{"PATH=" + os.Getenv("PATH"), "HOME=/h"}, "env")
if r.Status != 0 || !strings.Contains(r.Stdout, "HOME=/h") || !strings.Contains(r.Stdout, "PATH=") || strings.Contains(r.Stdout, "MESH_TEST_SECRET") {
t.Errorf("ran = %+v", r)
}
home, gone := t.TempDir(), t.TempDir()
t.Chdir(gone)
if err := os.Remove(gone); err != nil {
t.Fatal(err)
}
r = ExecRunnerClean(0)(context.Background(), []string{"PATH=" + os.Getenv("PATH"), "HOME=" + home}, "pwd")
if r.Status != 0 || strings.TrimSpace(r.Stdout) != home {
t.Errorf("a clean command run from a deleted directory = %+v, want it started in its HOME %s", r, home)
}
r = ExecRunnerFor(0)(context.Background(), nil, "pwd")
if r.Status != 0 || strings.TrimSpace(r.Stdout) != os.TempDir() {
t.Errorf("a command run from a deleted directory = %+v, want it started in %s", r, os.TempDir())
}
}
// 1497/AC5: ExecRunnerFor(0) is unchanged: the env argument is added to the process's environment.