github.com/iDigitalFlame/xmt@v0.5.4/cmd/exec_nix.go (about) 1 //go:build !windows && !js && !plan9 2 // +build !windows,!js,!plan9 3 4 // Copyright (C) 2020 - 2023 iDigitalFlame 5 // 6 // This program is free software: you can redistribute it and/or modify 7 // it under the terms of the GNU General Public License as published by 8 // the Free Software Foundation, either version 3 of the License, or 9 // any later version. 10 // 11 // This program is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU General Public License for more details. 15 // 16 // You should have received a copy of the GNU General Public License 17 // along with this program. If not, see <https://www.gnu.org/licenses/>. 18 // 19 20 package cmd 21 22 import ( 23 "context" 24 "io" 25 "os" 26 "os/exec" 27 "sync/atomic" 28 "syscall" 29 30 "github.com/iDigitalFlame/xmt/cmd/filter" 31 "github.com/iDigitalFlame/xmt/util/xerr" 32 ) 33 34 const ( 35 flagUID = 1 << 1 36 flagGID = 1 << 2 37 flagChroot = 1 << 3 38 flagSuspend = 1 << 4 39 ) 40 41 type executable struct { 42 e *exec.Cmd 43 c string 44 r *os.File 45 closers []io.Closer 46 uid, gid uint32 47 } 48 49 func (e *executable) close() { 50 if len(e.closers) > 0 { 51 for i := range e.closers { 52 e.closers[i].Close() 53 } 54 } 55 // NOTE(dij): This causes *nix systems to create a Zombie process 56 // (not what we want). Not sure if it matters enough to fix 57 // tho. 58 // if e.e.Process != nil { 59 // e.e.Process.Release() 60 // } 61 } 62 func (e *executable) Pid() uint32 { 63 if e.e.ProcessState != nil { 64 return uint32(e.e.ProcessState.Pid()) 65 } 66 return uint32(e.e.Process.Pid) 67 } 68 69 // ResumeProcess will attempt to resume the process via its PID. This will 70 // attempt to resume the process using an OS-dependent syscall. 71 // 72 // This will not affect already running processes. 73 func ResumeProcess(p uint32) error { 74 return syscall.Kill(int(p), syscall.SIGCONT) 75 } 76 func (executable) Handle() uintptr { 77 return 0 78 } 79 80 // SuspendProcess will attempt to suspend the process via its PID. This will 81 // attempt to suspend the process using an OS-dependent syscall. 82 // 83 // This will not affect already suspended processes. 84 func SuspendProcess(p uint32) error { 85 return syscall.Kill(int(p), syscall.SIGSTOP) 86 } 87 func (e *executable) Resume() error { 88 return e.e.Process.Signal(syscall.SIGCONT) 89 } 90 func (e *executable) Suspend() error { 91 return e.e.Process.Signal(syscall.SIGSTOP) 92 } 93 func (e *executable) isStarted() bool { 94 return e.e != nil && e.e.Process != nil 95 } 96 func (e *executable) isRunning() bool { 97 return e.isStarted() && e.e.ProcessState == nil 98 } 99 func (e *executable) wait(p *Process) { 100 err := e.e.Wait() 101 if _, ok := err.(*exec.ExitError); err != nil && !ok { 102 p.stopWith(exitStopped, err) 103 return 104 } 105 if err2 := p.ctx.Err(); err2 != nil { 106 p.stopWith(exitStopped, err2) 107 return 108 } 109 if atomic.StoreUint32(&p.cookie, atomic.LoadUint32(&p.cookie)|cookieStopped); e.e.ProcessState != nil { 110 p.exit = uint32(e.e.ProcessState.Sys().(syscall.WaitStatus).ExitStatus()) 111 } 112 if p.exit != 0 { 113 p.stopWith(p.exit, &ExitError{Exit: p.exit}) 114 return 115 } 116 p.stopWith(p.exit, nil) 117 } 118 func (executable) SetToken(_ uintptr) {} 119 func (executable) SetFullscreen(_ bool) {} 120 func (executable) SetWindowDisplay(_ int) {} 121 func (executable) SetWindowTitle(_ string) {} 122 func (executable) SetLogin(_, _, _ string) {} 123 func (executable) SetWindowSize(_, _ uint32) {} 124 func (e *executable) SetUID(u int32, p *Process) { 125 if u < 0 { 126 p.flags, e.uid = p.flags&^flagUID, 0 127 } else { 128 e.uid = uint32(u) 129 p.flags |= flagUID 130 } 131 } 132 func (e *executable) SetGID(g int32, p *Process) { 133 if g < 0 { 134 p.flags, e.gid = p.flags&^flagGID, 0 135 } else { 136 e.gid = uint32(g) 137 p.flags |= flagGID 138 } 139 } 140 func (executable) SetWindowPosition(_, _ uint32) {} 141 func (executable) SetNoWindow(_ bool, _ *Process) {} 142 func (executable) SetDetached(_ bool, _ *Process) {} 143 func (executable) SetSuspended(s bool, p *Process) { 144 if s { 145 p.flags |= flagSuspend 146 } else { 147 p.flags = flagSuspend 148 } 149 } 150 func (executable) SetNewConsole(_ bool, _ *Process) {} 151 func (e *executable) SetChroot(s string, p *Process) { 152 if len(s) == 0 { 153 p.flags, e.c = p.flags&^flagChroot, "" 154 } else { 155 e.c = s 156 p.flags |= flagChroot 157 } 158 } 159 func (e *executable) kill(x uint32, p *Process) error { 160 if p.exit = x; e.e == nil || e.e.Process == nil { 161 return p.err 162 } 163 return e.e.Process.Kill() 164 } 165 func (executable) SetParent(_ *filter.Filter, _ *Process) {} 166 func (e *executable) StdinPipe(p *Process) (io.WriteCloser, error) { 167 var err error 168 if p.Stdin, e.r, err = os.Pipe(); err != nil { 169 return nil, xerr.Wrap("unable to create Pipe", err) 170 } 171 e.closers = append(e.closers, p.Stdin.(io.Closer)) 172 return e.r, nil 173 } 174 func (e *executable) StdoutPipe(p *Process) (io.ReadCloser, error) { 175 r, w, err := os.Pipe() 176 if err != nil { 177 return nil, xerr.Wrap("unable to create Pipe", err) 178 } 179 p.Stdout = w 180 e.closers = append(e.closers, w) 181 return r, nil 182 } 183 func (e *executable) StderrPipe(p *Process) (io.ReadCloser, error) { 184 r, w, err := os.Pipe() 185 if err != nil { 186 return nil, xerr.Wrap("unable to create Pipe", err) 187 } 188 p.Stderr = w 189 e.closers = append(e.closers, w) 190 return r, nil 191 } 192 func (e *executable) start(x context.Context, p *Process, _ bool) error { 193 if e.e != nil { 194 return ErrAlreadyStarted 195 } 196 e.e = exec.CommandContext(x, p.Args[0]) 197 e.e.Dir, e.e.Env = p.Dir, p.Env 198 e.e.Stdin, e.e.Stdout, e.e.Stderr = p.Stdin, p.Stdout, p.Stderr 199 if e.e.Args = p.Args; !p.split { 200 z := os.Environ() 201 if e.e.Env == nil { 202 e.e.Env = make([]string, 0, len(z)) 203 } 204 e.e.Env = append(e.e.Env, z...) 205 } 206 if p.flags > 0 && p.flags != flagSuspend { 207 e.e.SysProcAttr = &syscall.SysProcAttr{Chroot: e.c} 208 switch { 209 case p.flags&flagUID != 0 && p.flags&flagGID != 0: 210 e.e.SysProcAttr.Credential = &syscall.Credential{Uid: e.uid, Gid: e.gid} 211 case p.flags&flagUID != 0 && p.flags&flagGID == 0: 212 e.e.SysProcAttr.Credential = &syscall.Credential{Uid: e.uid, Gid: uint32(os.Getgid())} 213 case p.flags&flagUID == 0 && p.flags&flagGID != 0: 214 e.e.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(os.Getuid()), Gid: e.gid} 215 } 216 } 217 if e.r != nil { 218 e.r.Close() 219 e.r = nil 220 } 221 if err := e.e.Start(); err != nil { 222 return err 223 } 224 if p.flags&flagSuspend != 0 { 225 syscall.Kill(int(e.Pid()), syscall.SIGSTOP) 226 } 227 go e.wait(p) 228 return nil 229 }