返回 DeepSeek-Reasonix
shell_launch.go
根目录 / internal / sandbox / shell_launch.go
1 package sandbox
2
3 import (
4 "context"
5 "os"
6 "os/exec"
7 "strings"
8 "sync"
9 "time"
10
11 "reasonix/internal/proc"
12 "reasonix/internal/secrets"
13 )
14
15 // powerShellLaunches reports whether a found PowerShell can start. A Store
16 // install is an app execution alias: a zero-byte reparse point (irregular to
17 // os.Lstat) that stats fine yet can fail to start the packaged app for this
18 // token. Only such a path pays a launch; a regular file is taken as found.
19 func powerShellLaunches(p string) bool {
20 fi, err := os.Lstat(p)
21 if err != nil || fi.Mode()&os.ModeIrregular == 0 {
22 return true
23 }
24 return aliasLaunches.check(p)
25 }
26
27 // launchablePowerShellLookPath hides a PowerShell on PATH that cannot start, so
28 // discovery falls through to the next interpreter instead of selecting it.
29 func launchablePowerShellLookPath(lookPath func(string) (string, error), launches func(string) bool) func(string) (string, error) {
30 return func(name string) (string, error) {
31 p, err := lookPath(name)
32 if err == nil && isPowerShellName(name) && !launches(p) {
33 return "", &exec.Error{Name: name, Err: exec.ErrNotFound}
34 }
35 return p, err
36 }
37 }
38
39 func launchablePowerShells(candidates []string, launches func(string) bool) []string {
40 out := make([]string, 0, len(candidates))
41 for _, p := range candidates {
42 if launches(p) {
43 out = append(out, p)
44 }
45 }
46 return out
47 }
48
49 func isPowerShellName(name string) bool {
50 base := strings.TrimSuffix(strings.ToLower(pathBase(name)), ".exe")
51 return base == "pwsh" || base == "powershell"
52 }
53
54 // launchProbeTTL bounds how long one alias probe answers the several discovery
55 // passes a session build makes.
56 const launchProbeTTL = 30 * time.Second
57
58 type launchProbe struct {
59 at time.Time
60 ok bool
61 }
62
63 type launchProbes struct {
64 mu sync.Mutex
65 seen map[string]launchProbe
66 run func(string) bool
67 }
68
69 var aliasLaunches = &launchProbes{seen: map[string]launchProbe{}, run: probePowerShell}
70
71 func (l *launchProbes) check(p string) bool {
72 key := strings.ToLower(p)
73 l.mu.Lock()
74 r, hit := l.seen[key]
75 l.mu.Unlock()
76 if hit && time.Since(r.at) < launchProbeTTL {
77 return r.ok
78 }
79 ok := l.run(p)
80 l.mu.Lock()
81 l.seen[key] = launchProbe{at: time.Now(), ok: ok}
82 l.mu.Unlock()
83 return ok
84 }
85
86 func (l *launchProbes) reset() {
87 l.mu.Lock()
88 defer l.mu.Unlock()
89 clear(l.seen)
90 }
91
92 func probePowerShell(path string) bool {
93 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
94 defer cancel()
95 return powerShellProbeCommand(ctx, path).Run() == nil
96 }
97
98 // powerShellProbeCommand starts nothing of the user's: no profile, a scrubbed
99 // environment, and a working directory outside the workspace.
100 func powerShellProbeCommand(ctx context.Context, path string) *exec.Cmd {
101 cmd := proc.CommandContext(ctx, path, "-NoLogo", "-NoProfile", "-NonInteractive", "-Command", "exit 0")
102 cmd.Env = secrets.ProcessEnv()
103 cmd.Dir = os.TempDir()
104 proc.HideWindow(cmd)
105 return cmd
106 }
107
107 lines GO