github.com/apptainer/singularity@v3.1.1+incompatible/internal/pkg/runtime/engines/oci/process.go (about)

     1  // Copyright (c) 2018, Sylabs Inc. All rights reserved.
     2  // This software is licensed under a 3-clause BSD license. Please consult the
     3  // LICENSE.md file distributed with the sources of this project regarding your
     4  // rights to use or distribute this software.
     5  
     6  package oci
     7  
     8  import (
     9  	"encoding/json"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"net"
    14  	"os"
    15  	osexec "os/exec"
    16  	"os/signal"
    17  	"path/filepath"
    18  	"strconv"
    19  	"strings"
    20  	"syscall"
    21  
    22  	"github.com/sylabs/singularity/pkg/util/copy"
    23  
    24  	"github.com/kr/pty"
    25  
    26  	specs "github.com/opencontainers/runtime-spec/specs-go"
    27  	"github.com/sylabs/singularity/pkg/ociruntime"
    28  	"github.com/sylabs/singularity/pkg/util/rlimit"
    29  	"github.com/sylabs/singularity/pkg/util/unix"
    30  
    31  	"github.com/sylabs/singularity/internal/pkg/instance"
    32  	"github.com/sylabs/singularity/internal/pkg/util/exec"
    33  
    34  	"github.com/sylabs/singularity/internal/pkg/security"
    35  	"github.com/sylabs/singularity/internal/pkg/sylog"
    36  )
    37  
    38  func setRlimit(rlimits []specs.POSIXRlimit) error {
    39  	var resources []string
    40  
    41  	for _, rl := range rlimits {
    42  		if err := rlimit.Set(rl.Type, rl.Soft, rl.Hard); err != nil {
    43  			return err
    44  		}
    45  		for _, t := range resources {
    46  			if t == rl.Type {
    47  				return fmt.Errorf("%s was already set", t)
    48  			}
    49  		}
    50  		resources = append(resources, rl.Type)
    51  	}
    52  
    53  	return nil
    54  }
    55  
    56  func (engine *EngineOperations) emptyProcess(masterConn net.Conn) error {
    57  	// pause process on next read
    58  	if _, err := masterConn.Write([]byte("t")); err != nil {
    59  		return fmt.Errorf("failed to pause process: %s", err)
    60  	}
    61  
    62  	// block on read start given
    63  	data := make([]byte, 1)
    64  	if _, err := masterConn.Read(data); err != nil {
    65  		return fmt.Errorf("failed to receive ack from master: %s", err)
    66  	}
    67  
    68  	var status syscall.WaitStatus
    69  	signals := make(chan os.Signal, 1)
    70  	signal.Notify(signals, syscall.SIGCHLD, syscall.SIGINT, syscall.SIGTERM)
    71  
    72  	if err := security.Configure(&engine.EngineConfig.OciConfig.Spec); err != nil {
    73  		return fmt.Errorf("failed to apply security configuration: %s", err)
    74  	}
    75  
    76  	masterConn.Close()
    77  
    78  	for {
    79  		s := <-signals
    80  		switch s {
    81  		case syscall.SIGCHLD:
    82  			for {
    83  				if pid, _ := syscall.Wait4(-1, &status, syscall.WNOHANG, nil); pid <= 0 {
    84  					break
    85  				}
    86  			}
    87  		case syscall.SIGINT, syscall.SIGTERM:
    88  			os.Exit(0)
    89  		}
    90  	}
    91  }
    92  
    93  // StartProcess starts the process
    94  func (engine *EngineOperations) StartProcess(masterConn net.Conn) error {
    95  	cwd := engine.EngineConfig.OciConfig.Process.Cwd
    96  
    97  	if cwd == "" {
    98  		cwd = "/"
    99  	}
   100  
   101  	if !filepath.IsAbs(cwd) {
   102  		return fmt.Errorf("cwd property must be an absolute path")
   103  	}
   104  
   105  	if err := os.Chdir(cwd); err != nil {
   106  		return fmt.Errorf("can't enter in current working directory: %s", err)
   107  	}
   108  
   109  	if err := setRlimit(engine.EngineConfig.OciConfig.Process.Rlimits); err != nil {
   110  		return err
   111  	}
   112  
   113  	if engine.EngineConfig.EmptyProcess {
   114  		return engine.emptyProcess(masterConn)
   115  	}
   116  
   117  	args := engine.EngineConfig.OciConfig.Process.Args
   118  	env := engine.EngineConfig.OciConfig.Process.Env
   119  
   120  	for _, e := range engine.EngineConfig.OciConfig.Process.Env {
   121  		if strings.HasPrefix(e, "PATH=") {
   122  			os.Setenv("PATH", e[5:])
   123  		}
   124  	}
   125  
   126  	bpath, err := osexec.LookPath(args[0])
   127  	if err != nil {
   128  		return fmt.Errorf("%s", err)
   129  	}
   130  	args[0] = bpath
   131  
   132  	if engine.EngineConfig.MasterPts != -1 {
   133  		slaveFd := engine.EngineConfig.SlavePts
   134  		if err := syscall.Dup3(slaveFd, int(os.Stdin.Fd()), 0); err != nil {
   135  			return err
   136  		}
   137  		if err := syscall.Dup3(slaveFd, int(os.Stdout.Fd()), 0); err != nil {
   138  			return err
   139  		}
   140  		if err := syscall.Dup3(slaveFd, int(os.Stderr.Fd()), 0); err != nil {
   141  			return err
   142  		}
   143  		if err := syscall.Close(engine.EngineConfig.MasterPts); err != nil {
   144  			return err
   145  		}
   146  		if err := syscall.Close(slaveFd); err != nil {
   147  			return err
   148  		}
   149  		if _, err := syscall.Setsid(); err != nil {
   150  			return err
   151  		}
   152  		if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, os.Stdin.Fd(), uintptr(syscall.TIOCSCTTY), 1); err != 0 {
   153  			return fmt.Errorf("failed to set crontrolling terminal: %s", err.Error())
   154  		}
   155  	} else if engine.EngineConfig.OutputStreams[1] != -1 {
   156  		if err := syscall.Dup3(engine.EngineConfig.OutputStreams[1], int(os.Stdout.Fd()), 0); err != nil {
   157  			return err
   158  		}
   159  		if err := syscall.Close(engine.EngineConfig.OutputStreams[1]); err != nil {
   160  			return err
   161  		}
   162  		if err := syscall.Close(engine.EngineConfig.OutputStreams[0]); err != nil {
   163  			return err
   164  		}
   165  
   166  		if err := syscall.Dup3(engine.EngineConfig.ErrorStreams[1], int(os.Stderr.Fd()), 0); err != nil {
   167  			return err
   168  		}
   169  		if err := syscall.Close(engine.EngineConfig.ErrorStreams[1]); err != nil {
   170  			return err
   171  		}
   172  		if err := syscall.Close(engine.EngineConfig.ErrorStreams[0]); err != nil {
   173  			return err
   174  		}
   175  
   176  		if err := syscall.Dup3(engine.EngineConfig.InputStreams[1], int(os.Stdin.Fd()), 0); err != nil {
   177  			return err
   178  		}
   179  		if err := syscall.Close(engine.EngineConfig.InputStreams[1]); err != nil {
   180  			return err
   181  		}
   182  		if err := syscall.Close(engine.EngineConfig.InputStreams[0]); err != nil {
   183  			return err
   184  		}
   185  	}
   186  
   187  	// trigger pre-start process
   188  	if _, err := masterConn.Write([]byte("t")); err != nil {
   189  		return fmt.Errorf("failed to pause process: %s", err)
   190  	}
   191  	if !engine.EngineConfig.Exec {
   192  		// block on read start given
   193  		data := make([]byte, 1)
   194  		if _, err := masterConn.Read(data); err != nil {
   195  			return fmt.Errorf("failed to receive start signal: %s", err)
   196  		}
   197  	}
   198  
   199  	if err := security.Configure(&engine.EngineConfig.OciConfig.Spec); err != nil {
   200  		return fmt.Errorf("failed to apply security configuration: %s", err)
   201  	}
   202  
   203  	err = syscall.Exec(args[0], args, env)
   204  	return fmt.Errorf("exec %s failed: %s", args[0], err)
   205  }
   206  
   207  // PreStartProcess will be executed in master context
   208  func (engine *EngineOperations) PreStartProcess(pid int, masterConn net.Conn, fatalChan chan error) error {
   209  	if engine.EngineConfig.Exec {
   210  		return nil
   211  	}
   212  
   213  	file, err := instance.Get(engine.CommonConfig.ContainerID, instance.OciSubDir)
   214  	if err != nil {
   215  		return err
   216  	}
   217  	engine.EngineConfig.State.AttachSocket = filepath.Join(filepath.Dir(file.Path), "attach.sock")
   218  
   219  	attach, err := unix.CreateSocket(engine.EngineConfig.State.AttachSocket)
   220  	if err != nil {
   221  		return err
   222  	}
   223  
   224  	engine.EngineConfig.State.ControlSocket = filepath.Join(filepath.Dir(file.Path), "control.sock")
   225  
   226  	control, err := unix.CreateSocket(engine.EngineConfig.State.ControlSocket)
   227  	if err != nil {
   228  		return err
   229  	}
   230  
   231  	logPath := engine.EngineConfig.GetLogPath()
   232  	if logPath == "" {
   233  		containerID := engine.CommonConfig.ContainerID
   234  		dir, err := instance.GetDirPrivileged(containerID, instance.OciSubDir)
   235  		if err != nil {
   236  			return err
   237  		}
   238  		logPath = filepath.Join(dir, containerID+".log")
   239  	}
   240  
   241  	format := engine.EngineConfig.GetLogFormat()
   242  	formatter, ok := instance.LogFormats[format]
   243  	if !ok {
   244  		return fmt.Errorf("log format %s is not supported", format)
   245  	}
   246  
   247  	logger, err := instance.NewLogger(logPath, formatter)
   248  	if err != nil {
   249  		return err
   250  	}
   251  
   252  	pidFile := engine.EngineConfig.GetPidFile()
   253  	if pidFile != "" {
   254  		if err := ioutil.WriteFile(pidFile, []byte(strconv.Itoa(pid)), 0644); err != nil {
   255  			return err
   256  		}
   257  	}
   258  
   259  	if err := engine.updateState(ociruntime.Created); err != nil {
   260  		return err
   261  	}
   262  
   263  	start := make(chan bool, 1)
   264  
   265  	go engine.handleControl(masterConn, attach, control, logger, start, fatalChan)
   266  
   267  	hooks := engine.EngineConfig.OciConfig.Hooks
   268  	if hooks != nil {
   269  		for _, h := range hooks.Prestart {
   270  			if err := exec.Hook(&h, &engine.EngineConfig.State.State); err != nil {
   271  				return err
   272  			}
   273  		}
   274  	}
   275  
   276  	// detach process
   277  	syscall.Kill(os.Getppid(), syscall.SIGUSR1)
   278  
   279  	// block until start event received
   280  	<-start
   281  	close(start)
   282  
   283  	return nil
   284  }
   285  
   286  // PostStartProcess will execute code in master context after execution of container
   287  // process, typically to write instance state/config files or execute post start OCI hook
   288  func (engine *EngineOperations) PostStartProcess(pid int) error {
   289  	if err := engine.updateState(ociruntime.Running); err != nil {
   290  		return err
   291  	}
   292  	hooks := engine.EngineConfig.OciConfig.Hooks
   293  	if hooks != nil {
   294  		for _, h := range hooks.Poststart {
   295  			if err := exec.Hook(&h, &engine.EngineConfig.State.State); err != nil {
   296  				sylog.Warningf("%s", err)
   297  			}
   298  		}
   299  	}
   300  	return nil
   301  }
   302  
   303  func (engine *EngineOperations) handleStream(l net.Listener, logger *instance.Logger, fatalChan chan error) {
   304  	var stdout io.ReadWriteCloser
   305  	var stderr io.ReadCloser
   306  	var stdin io.WriteCloser
   307  	var outputWriters *copy.MultiWriter
   308  	var errorWriters *copy.MultiWriter
   309  	var inputWriters *copy.MultiWriter
   310  	var tbuf *copy.TerminalBuffer
   311  
   312  	hasTerminal := engine.EngineConfig.OciConfig.Process.Terminal
   313  
   314  	inputWriters = &copy.MultiWriter{}
   315  	outputWriters = &copy.MultiWriter{}
   316  	outputWriters.Add(logger.NewWriter("stdout", true))
   317  
   318  	if hasTerminal {
   319  		stdout = os.NewFile(uintptr(engine.EngineConfig.MasterPts), "stream-master-pts")
   320  		tbuf = copy.NewTerminalBuffer()
   321  		outputWriters.Add(tbuf)
   322  		inputWriters.Add(stdout)
   323  	} else {
   324  		outputStream := os.NewFile(uintptr(engine.EngineConfig.OutputStreams[0]), "stdout-stream")
   325  		errorStream := os.NewFile(uintptr(engine.EngineConfig.ErrorStreams[0]), "error-stream")
   326  		inputStream := os.NewFile(uintptr(engine.EngineConfig.InputStreams[0]), "input-stream")
   327  		stdout = outputStream
   328  		stderr = errorStream
   329  		stdin = inputStream
   330  		outputWriters.Add(os.Stdout)
   331  		inputWriters.Add(stdin)
   332  	}
   333  
   334  	if stderr != nil {
   335  		errorWriters = &copy.MultiWriter{}
   336  		errorWriters.Add(logger.NewWriter("stderr", true))
   337  		errorWriters.Add(os.Stderr)
   338  	}
   339  
   340  	go func() {
   341  		for {
   342  			c, err := l.Accept()
   343  			if err != nil {
   344  				fatalChan <- err
   345  				return
   346  			}
   347  
   348  			go func() {
   349  				outputWriters.Add(c)
   350  				if stderr != nil {
   351  					errorWriters.Add(c)
   352  				}
   353  
   354  				if tbuf != nil {
   355  					c.Write(tbuf.Line())
   356  				}
   357  
   358  				io.Copy(inputWriters, c)
   359  
   360  				outputWriters.Del(c)
   361  				if stderr != nil {
   362  					errorWriters.Del(c)
   363  				}
   364  				c.Close()
   365  			}()
   366  		}
   367  	}()
   368  
   369  	go func() {
   370  		io.Copy(outputWriters, stdout)
   371  		stdout.Close()
   372  	}()
   373  
   374  	if stderr != nil {
   375  		go func() {
   376  			io.Copy(errorWriters, stderr)
   377  			stderr.Close()
   378  		}()
   379  	}
   380  	if stdin != nil {
   381  		go func() {
   382  			io.Copy(inputWriters, os.Stdin)
   383  			stdin.Close()
   384  		}()
   385  	}
   386  }
   387  
   388  func (engine *EngineOperations) handleControl(masterConn net.Conn, attach net.Listener, control net.Listener, logger *instance.Logger, start chan bool, fatalChan chan error) {
   389  	var master *os.File
   390  	started := false
   391  
   392  	if engine.EngineConfig.OciConfig.Process.Terminal {
   393  		master = os.NewFile(uintptr(engine.EngineConfig.MasterPts), "control-master-pts")
   394  	}
   395  
   396  	for {
   397  		c, err := control.Accept()
   398  		if err != nil {
   399  			fatalChan <- err
   400  			return
   401  		}
   402  		dec := json.NewDecoder(c)
   403  		ctrl := &ociruntime.Control{}
   404  		if err := dec.Decode(ctrl); err != nil {
   405  			fatalChan <- err
   406  			return
   407  		}
   408  
   409  		if ctrl.StartContainer && !started {
   410  			started = true
   411  
   412  			engine.handleStream(attach, logger, fatalChan)
   413  
   414  			// since container process block on read, send it an
   415  			// ACK so when it will receive data, the container
   416  			// process will be executed
   417  			if _, err := masterConn.Write([]byte("s")); err != nil {
   418  				fatalChan <- fmt.Errorf("failed to send ACK to start process: %s", err)
   419  				return
   420  			}
   421  
   422  			// send start event
   423  			start <- true
   424  
   425  			// wait status update
   426  			engine.waitStatusUpdate()
   427  		}
   428  		if ctrl.ConsoleSize != nil && master != nil {
   429  			size := &pty.Winsize{
   430  				Cols: uint16(ctrl.ConsoleSize.Width),
   431  				Rows: uint16(ctrl.ConsoleSize.Height),
   432  			}
   433  			if err := pty.Setsize(master, size); err != nil {
   434  				fatalChan <- err
   435  				return
   436  			}
   437  		}
   438  		if ctrl.ReopenLog {
   439  			logger.ReOpenFile()
   440  		}
   441  		if ctrl.Pause {
   442  			if err := engine.EngineConfig.Cgroups.Pause(); err != nil {
   443  				fatalChan <- err
   444  				return
   445  			}
   446  			if err := engine.updateState(ociruntime.Paused); err != nil {
   447  				fatalChan <- err
   448  				return
   449  			}
   450  		}
   451  		if ctrl.Resume {
   452  			if err := engine.updateState(ociruntime.Running); err != nil {
   453  				fatalChan <- err
   454  				return
   455  			}
   456  			if err := engine.EngineConfig.Cgroups.Resume(); err != nil {
   457  				fatalChan <- err
   458  				return
   459  			}
   460  		}
   461  
   462  		c.Close()
   463  	}
   464  }