...

Source file src/github.com/Microsoft/hcsshim/internal/guest/runtime/runc/runc.go

Documentation: github.com/Microsoft/hcsshim/internal/guest/runtime/runc

     1  //go:build linux
     2  // +build linux
     3  
     4  package runc
     5  
     6  import (
     7  	"encoding/json"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"strconv"
    12  	"strings"
    13  	"syscall"
    14  
    15  	oci "github.com/opencontainers/runtime-spec/specs-go"
    16  	"github.com/pkg/errors"
    17  	"golang.org/x/sys/unix"
    18  
    19  	"github.com/Microsoft/hcsshim/internal/guest/commonutils"
    20  	"github.com/Microsoft/hcsshim/internal/guest/runtime"
    21  	"github.com/Microsoft/hcsshim/internal/guest/stdio"
    22  )
    23  
    24  const (
    25  	containerFilesDir = "/var/run/gcsrunc"
    26  	initPidFilename   = "initpid"
    27  )
    28  
    29  func setSubreaper(i int) error {
    30  	return unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, uintptr(i), 0, 0, 0)
    31  }
    32  
    33  // NewRuntime instantiates a new runcRuntime struct.
    34  func NewRuntime(logBasePath string) (runtime.Runtime, error) {
    35  	rtime := &runcRuntime{runcLogBasePath: logBasePath}
    36  	if err := rtime.initialize(); err != nil {
    37  		return nil, err
    38  	}
    39  	return rtime, nil
    40  }
    41  
    42  // runcRuntime is an implementation of the Runtime interface which uses runC as
    43  // the container runtime.
    44  type runcRuntime struct {
    45  	runcLogBasePath string
    46  }
    47  
    48  var _ runtime.Runtime = &runcRuntime{}
    49  
    50  // initialize sets up any state necessary for the runcRuntime to function.
    51  func (r *runcRuntime) initialize() error {
    52  	paths := [2]string{containerFilesDir, r.runcLogBasePath}
    53  	for _, p := range paths {
    54  		_, err := os.Stat(p)
    55  		if err != nil {
    56  			if !os.IsNotExist(err) {
    57  				return err
    58  			}
    59  			if err := os.MkdirAll(p, 0700); err != nil {
    60  				return errors.Wrapf(err, "failed making runC container files directory %s", p)
    61  			}
    62  		}
    63  	}
    64  
    65  	return nil
    66  }
    67  
    68  // CreateContainer creates a container with the given ID and the given
    69  // bundlePath.
    70  // bundlePath should be a path to an OCI bundle containing a config.json file
    71  // and a rootfs for the container.
    72  func (r *runcRuntime) CreateContainer(id string, bundlePath string, stdioSet *stdio.ConnectionSet) (c runtime.Container, err error) {
    73  	c, err = r.runCreateCommand(id, bundlePath, stdioSet)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  	return c, nil
    78  }
    79  
    80  // ListContainerStates returns ContainerState structs for all existing
    81  // containers, whether they're running or not.
    82  func (*runcRuntime) ListContainerStates() ([]runtime.ContainerState, error) {
    83  	cmd := runcCommand("list", "-f", "json")
    84  	out, err := cmd.CombinedOutput()
    85  	if err != nil {
    86  		runcErr := parseRuncError(string(out))
    87  		return nil, errors.Wrapf(runcErr, "runc list failed with %v: %s", err, string(out))
    88  	}
    89  	var states []runtime.ContainerState
    90  	if err := json.Unmarshal(out, &states); err != nil {
    91  		return nil, errors.Wrap(err, "failed to unmarshal the states for the container list")
    92  	}
    93  	return states, nil
    94  }
    95  
    96  // getRunningPids gets the pids of all processes which runC recognizes as
    97  // running.
    98  func (*runcRuntime) getRunningPids(id string) ([]int, error) {
    99  	cmd := runcCommand("ps", "-f", "json", id)
   100  	out, err := cmd.CombinedOutput()
   101  	if err != nil {
   102  		runcErr := parseRuncError(string(out))
   103  		return nil, errors.Wrapf(runcErr, "runc ps failed with %v: %s", err, string(out))
   104  	}
   105  	var pids []int
   106  	if err := json.Unmarshal(out, &pids); err != nil {
   107  		return nil, errors.Wrapf(err, "failed to unmarshal pids for container %s", id)
   108  	}
   109  	return pids, nil
   110  }
   111  
   112  // getProcessCommand gets the command line command and arguments for the process
   113  // with the given pid.
   114  func (*runcRuntime) getProcessCommand(pid int) ([]string, error) {
   115  	// Get the contents of the process's cmdline file. This file is formatted
   116  	// with a null character after every argument. e.g. "ping google.com "
   117  	data, err := os.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "cmdline"))
   118  	if err != nil {
   119  		return nil, errors.Wrapf(err, "failed to read cmdline file for process %d", pid)
   120  	}
   121  	// Get rid of the \0 character at end.
   122  	cmdString := strings.TrimSuffix(string(data), "\x00")
   123  	return strings.Split(cmdString, "\x00"), nil
   124  }
   125  
   126  // pidMapToProcessStates is a helper function which converts a map from pid to
   127  // ContainerProcessState to a slice of ContainerProcessStates.
   128  func (*runcRuntime) pidMapToProcessStates(pidMap map[int]*runtime.ContainerProcessState) []runtime.ContainerProcessState {
   129  	processStates := make([]runtime.ContainerProcessState, len(pidMap))
   130  	i := 0
   131  	for _, processState := range pidMap {
   132  		processStates[i] = *processState
   133  		i++
   134  	}
   135  	return processStates
   136  }
   137  
   138  // waitOnProcess waits for the process to exit, and returns its exit code.
   139  func (r *runcRuntime) waitOnProcess(pid int) (int, error) {
   140  	process, err := os.FindProcess(pid)
   141  	if err != nil {
   142  		return -1, errors.Wrapf(err, "failed to find process %d", pid)
   143  	}
   144  	state, err := process.Wait()
   145  	if err != nil {
   146  		return -1, errors.Wrapf(err, "failed waiting on process %d", pid)
   147  	}
   148  
   149  	status := state.Sys().(syscall.WaitStatus)
   150  	if status.Signaled() {
   151  		return 128 + int(status.Signal()), nil
   152  	}
   153  	return status.ExitStatus(), nil
   154  }
   155  
   156  // runCreateCommand sets up the arguments for calling runc create.
   157  func (r *runcRuntime) runCreateCommand(id string, bundlePath string, stdioSet *stdio.ConnectionSet) (runtime.Container, error) {
   158  	c := &container{r: r, id: id}
   159  	if err := r.makeContainerDir(id); err != nil {
   160  		return nil, err
   161  	}
   162  	// Create a temporary random directory to store the process's files.
   163  	tempProcessDir, err := os.MkdirTemp(containerFilesDir, id)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	spec, err := ociSpecFromBundle(bundlePath)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  
   173  	// Determine if the container owns its own pid namespace or not. Per the OCI
   174  	// spec:
   175  	// - If the spec has no entry for the pid namespace, the container inherits
   176  	//   the runtime namespace (container does not own).
   177  	// - If the spec has a pid namespace entry, but the path is empty, a new
   178  	//   namespace will be created and used for the container (container owns).
   179  	// - If there is a pid namespace entry with a path, the container uses the
   180  	//   namespace at that path (container does not own).
   181  	if spec.Linux != nil {
   182  		for _, ns := range spec.Linux.Namespaces {
   183  			if ns.Type == oci.PIDNamespace {
   184  				c.ownsPidNamespace = ns.Path == ""
   185  			}
   186  		}
   187  	}
   188  
   189  	if spec.Process.Cwd != "/" {
   190  		cwd := path.Join(bundlePath, "rootfs", spec.Process.Cwd)
   191  		// Intentionally ignore the error.
   192  		_ = os.MkdirAll(cwd, 0755)
   193  	}
   194  
   195  	args := []string{"create", "-b", bundlePath, "--no-pivot"}
   196  
   197  	p, err := c.startProcess(tempProcessDir, spec.Process.Terminal, stdioSet, spec.Annotations, args...)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	// Write pid to initpid file for container.
   203  	containerDir := r.getContainerDir(id)
   204  	if err := os.WriteFile(filepath.Join(containerDir, initPidFilename), []byte(strconv.Itoa(p.pid)), 0777); err != nil {
   205  		return nil, err
   206  	}
   207  
   208  	c.init = p
   209  	return c, nil
   210  }
   211  
   212  func ociSpecFromBundle(bundlePath string) (*oci.Spec, error) {
   213  	configPath := filepath.Join(bundlePath, "config.json")
   214  	configFile, err := os.Open(configPath)
   215  	if err != nil {
   216  		return nil, errors.Wrapf(err, "failed to open bundle config at %s", configPath)
   217  	}
   218  	defer configFile.Close()
   219  	var spec *oci.Spec
   220  	if err := commonutils.DecodeJSONWithHresult(configFile, &spec); err != nil {
   221  		return nil, errors.Wrap(err, "failed to parse OCI spec")
   222  	}
   223  	return spec, nil
   224  }
   225  

View as plain text