github.com/opencontainers/runc@v1.2.0-rc.1.0.20240520010911-492dc558cdd6/libcontainer/factory_linux.go (about)

     1  package libcontainer
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  
     9  	securejoin "github.com/cyphar/filepath-securejoin"
    10  	"golang.org/x/sys/unix"
    11  
    12  	"github.com/opencontainers/runc/libcontainer/cgroups/manager"
    13  	"github.com/opencontainers/runc/libcontainer/configs"
    14  	"github.com/opencontainers/runc/libcontainer/configs/validate"
    15  	"github.com/opencontainers/runc/libcontainer/intelrdt"
    16  	"github.com/opencontainers/runc/libcontainer/utils"
    17  )
    18  
    19  const (
    20  	stateFilename    = "state.json"
    21  	execFifoFilename = "exec.fifo"
    22  )
    23  
    24  // Create creates a new container with the given id inside a given state
    25  // directory (root), and returns a Container object.
    26  //
    27  // The root is a state directory which many containers can share. It can be
    28  // used later to get the list of containers, or to get information about a
    29  // particular container (see Load).
    30  //
    31  // The id must not be empty and consist of only the following characters:
    32  // ASCII letters, digits, underscore, plus, minus, period. The id must be
    33  // unique and non-existent for the given root path.
    34  func Create(root, id string, config *configs.Config) (*Container, error) {
    35  	if root == "" {
    36  		return nil, errors.New("root not set")
    37  	}
    38  	if err := validateID(id); err != nil {
    39  		return nil, err
    40  	}
    41  	if err := validate.Validate(config); err != nil {
    42  		return nil, err
    43  	}
    44  	if err := os.MkdirAll(root, 0o700); err != nil {
    45  		return nil, err
    46  	}
    47  	stateDir, err := securejoin.SecureJoin(root, id)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	if _, err := os.Stat(stateDir); err == nil {
    52  		return nil, ErrExist
    53  	} else if !os.IsNotExist(err) {
    54  		return nil, err
    55  	}
    56  
    57  	cm, err := manager.New(config.Cgroups)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	// Check that cgroup does not exist or empty (no processes).
    63  	// Note for cgroup v1 this check is not thorough, as there are multiple
    64  	// separate hierarchies, while both Exists() and GetAllPids() only use
    65  	// one for "devices" controller (assuming others are the same, which is
    66  	// probably true in almost all scenarios). Checking all the hierarchies
    67  	// would be too expensive.
    68  	if cm.Exists() {
    69  		pids, err := cm.GetAllPids()
    70  		// Reading PIDs can race with cgroups removal, so ignore ENOENT and ENODEV.
    71  		if err != nil && !errors.Is(err, os.ErrNotExist) && !errors.Is(err, unix.ENODEV) {
    72  			return nil, fmt.Errorf("unable to get cgroup PIDs: %w", err)
    73  		}
    74  		if len(pids) != 0 {
    75  			return nil, fmt.Errorf("container's cgroup is not empty: %d process(es) found", len(pids))
    76  		}
    77  	}
    78  
    79  	// Check that cgroup is not frozen. Do not use Exists() here
    80  	// since in cgroup v1 it only checks "devices" controller.
    81  	st, err := cm.GetFreezerState()
    82  	if err != nil {
    83  		return nil, fmt.Errorf("unable to get cgroup freezer state: %w", err)
    84  	}
    85  	if st == configs.Frozen {
    86  		return nil, errors.New("container's cgroup unexpectedly frozen")
    87  	}
    88  
    89  	// Parent directory is already created above, so Mkdir is enough.
    90  	if err := os.Mkdir(stateDir, 0o711); err != nil {
    91  		return nil, err
    92  	}
    93  	c := &Container{
    94  		id:              id,
    95  		stateDir:        stateDir,
    96  		config:          config,
    97  		cgroupManager:   cm,
    98  		intelRdtManager: intelrdt.NewManager(config, id, ""),
    99  	}
   100  	c.state = &stoppedState{c: c}
   101  	return c, nil
   102  }
   103  
   104  // Load takes a path to the state directory (root) and an id of an existing
   105  // container, and returns a Container object reconstructed from the saved
   106  // state. This presents a read only view of the container.
   107  func Load(root, id string) (*Container, error) {
   108  	if root == "" {
   109  		return nil, errors.New("root not set")
   110  	}
   111  	// when load, we need to check id is valid or not.
   112  	if err := validateID(id); err != nil {
   113  		return nil, err
   114  	}
   115  	stateDir, err := securejoin.SecureJoin(root, id)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  	state, err := loadState(stateDir)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	r := &nonChildProcess{
   124  		processPid:       state.InitProcessPid,
   125  		processStartTime: state.InitProcessStartTime,
   126  		fds:              state.ExternalDescriptors,
   127  	}
   128  	cm, err := manager.NewWithPaths(state.Config.Cgroups, state.CgroupPaths)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	c := &Container{
   133  		initProcess:          r,
   134  		initProcessStartTime: state.InitProcessStartTime,
   135  		id:                   id,
   136  		config:               &state.Config,
   137  		cgroupManager:        cm,
   138  		intelRdtManager:      intelrdt.NewManager(&state.Config, id, state.IntelRdtPath),
   139  		stateDir:             stateDir,
   140  		created:              state.Created,
   141  	}
   142  	c.state = &loadedState{c: c}
   143  	if err := c.refreshState(); err != nil {
   144  		return nil, err
   145  	}
   146  	return c, nil
   147  }
   148  
   149  func loadState(root string) (*State, error) {
   150  	stateFilePath, err := securejoin.SecureJoin(root, stateFilename)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  	f, err := os.Open(stateFilePath)
   155  	if err != nil {
   156  		if os.IsNotExist(err) {
   157  			return nil, ErrNotExist
   158  		}
   159  		return nil, err
   160  	}
   161  	defer f.Close()
   162  	var state *State
   163  	if err := json.NewDecoder(f).Decode(&state); err != nil {
   164  		return nil, err
   165  	}
   166  	return state, nil
   167  }
   168  
   169  // validateID checks if the supplied container ID is valid, returning
   170  // the ErrInvalidID in case it is not.
   171  //
   172  // The format of valid ID was never formally defined, instead the code
   173  // was modified to allow or disallow specific characters.
   174  //
   175  // Currently, a valid ID is a non-empty string consisting only of
   176  // the following characters:
   177  // - uppercase (A-Z) and lowercase (a-z) Latin letters;
   178  // - digits (0-9);
   179  // - underscore (_);
   180  // - plus sign (+);
   181  // - minus sign (-);
   182  // - period (.).
   183  //
   184  // In addition, IDs that can't be used to represent a file name
   185  // (such as . or ..) are rejected.
   186  
   187  func validateID(id string) error {
   188  	if len(id) < 1 {
   189  		return ErrInvalidID
   190  	}
   191  
   192  	// Allowed characters: 0-9 A-Z a-z _ + - .
   193  	for i := 0; i < len(id); i++ {
   194  		c := id[i]
   195  		switch {
   196  		case c >= 'a' && c <= 'z':
   197  		case c >= 'A' && c <= 'Z':
   198  		case c >= '0' && c <= '9':
   199  		case c == '_':
   200  		case c == '+':
   201  		case c == '-':
   202  		case c == '.':
   203  		default:
   204  			return ErrInvalidID
   205  		}
   206  
   207  	}
   208  
   209  	if string(os.PathSeparator)+id != utils.CleanPath(string(os.PathSeparator)+id) {
   210  		return ErrInvalidID
   211  	}
   212  
   213  	return nil
   214  }