turbo-editors/turbo-corepublic Fork 0
28d59854361aeda8541d853093e732126f3d7bff
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

shell_windows.go · 99 lines · 3.1 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 13h ago1//go:build windows
2
3package tools
4
5import (
6 "os"
7 "os/exec"
8 "syscall"
9 "unsafe"
10
11 "golang.org/x/sys/windows"
12)
13
14// shellProgram is the shell Windows names as the user's: %COMSPEC%, which is
15// cmd.exe.
16func shellProgram() string { return windowsShell(os.Getenv("COMSPEC")) }
17
18// shellArgs makes cmd.exe run one command line and exit. /S is what makes its
19// quote handling predictable; see cmdExeCommandLine.
20func shellArgs(command string) []string { return []string{"/S", "/C", command} }
21
22// childAttributes gives the command a process group of its own and, more to
23// the point, a command line written the way cmd.exe reads it.
24//
25// Go's exec composes a command line by the C runtime's rules, which cmd.exe
26// does not follow: a `"` inside the command would arrive as `\"`. So the line
27// is written here in full and exec is told to use it as it is.
28func childAttributes(process *exec.Cmd, command string) *syscall.SysProcAttr {
29 return &syscall.SysProcAttr{
30 CreationFlags: windows.CREATE_NEW_PROCESS_GROUP,
31 CmdLine: cmdExeCommandLine(process.Path, command),
32 }
33}
34
35// jobObject is the Windows answer to a process group: every process the
36// command starts is put in the job, and terminating the job ends them all.
37//
38// A job that could not be created leaves the command running as it would have
39// anyway, and kill falls back to the one process.
40type jobObject struct {
41 process *os.Process
42 job windows.Handle
43}
44
45// newGroup puts a started command in a job of its own.
46//
47// The process is assigned after it has started, so a child it spawned in the
48// meantime escapes the job. cmd.exe takes milliseconds to start a child and the
49// window is that wide; the alternative — starting suspended and resuming by
50// hand — is not something exec.Cmd allows.
51func newGroup(process *os.Process) group {
52 g := jobObject{process: process}
53
54 job, err := windows.CreateJobObject(nil, nil)
55 if err != nil {
56 return g
57 }
58 limits := windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION{}
59 limits.BasicLimitInformation.LimitFlags = windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
60 if _, err := windows.SetInformationJobObject(job, windows.JobObjectExtendedLimitInformation,
61 uintptr(unsafe.Pointer(&limits)), uint32(unsafe.Sizeof(limits))); err != nil {
62 windows.CloseHandle(job)
63 return g
64 }
65
66 handle, err := windows.OpenProcess(windows.PROCESS_SET_QUOTA|windows.PROCESS_TERMINATE, false, uint32(process.Pid))
67 if err != nil {
68 windows.CloseHandle(job)
69 return g
70 }
71 defer windows.CloseHandle(handle)
72 if err := windows.AssignProcessToJobObject(job, handle); err != nil {
73 windows.CloseHandle(job)
74 return g
75 }
76
77 g.job = job
78 return g
79}
80
81// kill terminates the job — the shell and everything it started — or, with no
82// job, the shell alone.
83func (g jobObject) kill() {
84 if g.job != 0 {
85 if err := windows.TerminateJobObject(g.job, 1); err == nil {
86 return
87 }
88 }
89 _ = g.process.Kill()
90}
91
92// release closes the job handle. The job was created to kill on close, so a
93// command that ended on its own has nothing left in it, and one that is still
94// running is ended — which is only reached from a Run that has finished.
95func (g jobObject) release() {
96 if g.job != 0 {
97 windows.CloseHandle(g.job)
98 }
99}