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  }