github.com/mgoltzsche/ctnr@v0.7.1-alpha/run/librunner/process.go (about)

     1  package librunner
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strconv"
     7  	"sync"
     8  	"syscall"
     9  	"time"
    10  
    11  	"github.com/coreos/go-systemd/activation"
    12  	exterrors "github.com/mgoltzsche/ctnr/pkg/errors"
    13  	"github.com/mgoltzsche/ctnr/pkg/log"
    14  	"github.com/mgoltzsche/ctnr/run"
    15  	"github.com/opencontainers/runc/libcontainer"
    16  	"github.com/opencontainers/runc/libcontainer/configs"
    17  	"github.com/opencontainers/runtime-spec/specs-go"
    18  	"github.com/pkg/errors"
    19  )
    20  
    21  type Process struct {
    22  	args      []string
    23  	container *Container
    24  	process   libcontainer.Process
    25  	io        run.ContainerIO
    26  	tty       *tty
    27  	terminal  bool
    28  	running   bool
    29  	mutex     *sync.Mutex
    30  	wait      *sync.WaitGroup
    31  	log       log.Loggers
    32  	err       error
    33  }
    34  
    35  func NewProcess(container *Container, p *specs.Process, io run.ContainerIO, loggers log.Loggers) (r *Process, err error) {
    36  	// Create container process (see https://github.com/opencontainers/runc/blob/v1.0.0-rc4/utils_linux.go: startContainer->runner.run->newProcess)
    37  	r = &Process{
    38  		args:      p.Args,
    39  		container: container,
    40  		io:        io,
    41  		terminal:  p.Terminal,
    42  		mutex:     &sync.Mutex{},
    43  		wait:      &sync.WaitGroup{},
    44  		log:       loggers,
    45  		process: libcontainer.Process{
    46  			Args:            p.Args,
    47  			Env:             p.Env,
    48  			User:            fmt.Sprintf("%d:%d", p.User.UID, p.User.GID),
    49  			Cwd:             p.Cwd,
    50  			Stdout:          io.Stdout,
    51  			Stderr:          io.Stderr,
    52  			Stdin:           io.Stdin,
    53  			AppArmorProfile: p.ApparmorProfile,
    54  			Label:           p.SelinuxLabel,
    55  			NoNewPrivileges: &p.NoNewPrivileges,
    56  		},
    57  	}
    58  	lp := r.process
    59  	for _, gid := range p.User.AdditionalGids {
    60  		lp.AdditionalGroups = append(lp.AdditionalGroups, strconv.FormatUint(uint64(gid), 10))
    61  	}
    62  	if p.Capabilities != nil {
    63  		lp.Capabilities = &configs.Capabilities{
    64  			Bounding:    p.Capabilities.Bounding,
    65  			Effective:   p.Capabilities.Effective,
    66  			Inheritable: p.Capabilities.Inheritable,
    67  			Permitted:   p.Capabilities.Permitted,
    68  			Ambient:     p.Capabilities.Ambient,
    69  		}
    70  	}
    71  	if p.Rlimits != nil {
    72  		for _, rlimit := range p.Rlimits {
    73  			rl, err := createLibContainerRlimit(rlimit)
    74  			if err != nil {
    75  				return nil, errors.New("new process: " + err.Error())
    76  			}
    77  			lp.Rlimits = append(lp.Rlimits, rl)
    78  		}
    79  	}
    80  	if os.Getenv("LISTEN_FDS") != "" {
    81  		// Add systemd file descriptors
    82  		lp.ExtraFiles = activation.Files(false)
    83  	}
    84  	return
    85  }
    86  
    87  func (p *Process) Start() (err error) {
    88  	if len(p.args) == 0 {
    89  		return errors.New("start process: no args specified")
    90  	}
    91  	p.log.Debug.WithField("args", p.args).Println("Starting process")
    92  
    93  	p.mutex.Lock()
    94  	defer p.mutex.Unlock()
    95  	defer exterrors.Wrapd(&err, "run process")
    96  
    97  	if p.running {
    98  		return errors.New("process already started")
    99  	}
   100  
   101  	// Configure stdIO/terminal
   102  	p.tty, err = setupIO(&p.process, p.container.container, p.terminal, false, "")
   103  	if err != nil {
   104  		return
   105  	}
   106  
   107  	// Run container process
   108  	if err = p.container.container.Run(&p.process); err != nil {
   109  		p.tty.Close()
   110  		return
   111  	}
   112  	p.running = true
   113  
   114  	p.wait.Add(1)
   115  	go p.handleTermination()
   116  	return nil
   117  }
   118  
   119  func (p *Process) handleTermination() {
   120  	defer p.wait.Done()
   121  
   122  	// Wait for process
   123  	_, err := p.process.Wait()
   124  
   125  	err = run.NewExitError(err, p.container.ID())
   126  	logger := p.log.Debug
   127  	if exiterr, ok := err.(*run.ExitError); ok {
   128  		logger = logger.WithField("code", exiterr.Code())
   129  	}
   130  	logger.WithField("args", p.args).Println("Process terminated")
   131  
   132  	p.mutex.Lock()
   133  	defer p.mutex.Unlock()
   134  
   135  	// Register process error
   136  	p.err = err
   137  
   138  	// Release TTY
   139  	// TODO: reject tty CLI option when process is detached and no console socket provided
   140  	err = p.tty.Close() // ATTENTION: deadlock when detached process and tty enabled
   141  	p.err = exterrors.Append(p.err, err)
   142  	p.tty = nil
   143  	p.running = false
   144  }
   145  
   146  func (c *Process) Stop() {
   147  	if c.running {
   148  		return
   149  	}
   150  
   151  	go c.stop()
   152  }
   153  
   154  func (p *Process) stop() bool {
   155  	// Terminate container orderly
   156  	p.mutex.Lock()
   157  
   158  	if !p.running {
   159  		p.mutex.Unlock()
   160  		return false
   161  	}
   162  
   163  	p.log.Debug.WithField("args", p.args).Println("Stopping process")
   164  
   165  	if err := p.process.Signal(syscall.SIGINT); err != nil {
   166  		p.log.Debug.Println("Failed to send SIGINT to process:", err)
   167  	}
   168  	p.mutex.Unlock()
   169  
   170  	quit := make(chan bool, 1)
   171  	go func() {
   172  		p.wait.Wait()
   173  		quit <- true
   174  	}()
   175  	select {
   176  	case <-time.After(time.Duration(10000000)): // TODO: read value from OCI runtime configuration
   177  		// Kill container after timeout
   178  		if p.running {
   179  			p.log.Warn.WithField("args", p.args).Println("Killing process (stop timeout exceeded)")
   180  			if err := p.process.Signal(syscall.SIGKILL); err != nil {
   181  				errlog := p.log.Error
   182  				if pid, e := p.process.Pid(); e == nil {
   183  					errlog = errlog.WithField("pid", pid)
   184  				}
   185  				errlog.Println("Failed to kill process:", err)
   186  			}
   187  			p.wait.Wait()
   188  		}
   189  		<-quit
   190  	case <-quit:
   191  	}
   192  	close(quit)
   193  	return true
   194  }
   195  
   196  // Waits for the container process to terminate and returns the process' error if any
   197  func (p *Process) Wait() (err error) {
   198  	p.wait.Wait()
   199  	p.mutex.Lock()
   200  	defer p.mutex.Unlock()
   201  	err = p.err
   202  	p.err = nil
   203  	return err
   204  }
   205  
   206  func (p *Process) Close() (err error) {
   207  	p.stop()
   208  	p.mutex.Lock()
   209  	defer p.mutex.Unlock()
   210  	if p.running {
   211  		err = p.err
   212  		p.err = nil
   213  		if p.tty != nil {
   214  			err = exterrors.Append(err, p.tty.Close())
   215  			p.tty = nil
   216  		}
   217  	}
   218  	return
   219  }