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 }