github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/runsc/container/state_file.go (about) 1 // Copyright 2019 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package container 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "path/filepath" 23 "regexp" 24 "strings" 25 26 "github.com/gofrs/flock" 27 "golang.org/x/sys/unix" 28 "github.com/SagerNet/gvisor/pkg/log" 29 "github.com/SagerNet/gvisor/pkg/sync" 30 ) 31 32 const stateFileExtension = "state" 33 34 // LoadOpts provides options for Load()ing a container. 35 type LoadOpts struct { 36 // Exact tells whether the search should be exact. See Load() for more. 37 Exact bool 38 39 // SkipCheck tells Load() to skip checking if container is runnning. 40 SkipCheck bool 41 } 42 43 // Load loads a container with the given id from a metadata file. "id" may 44 // be an abbreviation of the full container id in case LoadOpts.Exact if not 45 // set. It also checks if the container is still running, in order to return 46 // an error to the caller earlier. This check is skipped if LoadOpts.SkipCheck 47 // is set. 48 // 49 // Returns ErrNotExist if no container is found. Returns error in case more than 50 // one containers matching the ID prefix is found. 51 func Load(rootDir string, id FullID, opts LoadOpts) (*Container, error) { 52 log.Debugf("Load container, rootDir: %q, id: %+v, opts: %+v", rootDir, id, opts) 53 if !opts.Exact { 54 var err error 55 id, err = findContainerID(rootDir, id.ContainerID) 56 if err != nil { 57 // Preserve error so that callers can distinguish 'not found' errors. 58 return nil, err 59 } 60 } 61 62 if err := id.validate(); err != nil { 63 return nil, fmt.Errorf("invalid container id: %v", err) 64 } 65 state := StateFile{ 66 RootDir: rootDir, 67 ID: id, 68 } 69 defer state.close() 70 71 c := &Container{} 72 if err := state.load(c); err != nil { 73 if os.IsNotExist(err) { 74 // Preserve error so that callers can distinguish 'not found' errors. 75 return nil, err 76 } 77 return nil, fmt.Errorf("reading container metadata file %q: %v", state.statePath(), err) 78 } 79 80 if !opts.SkipCheck { 81 // If the status is "Running" or "Created", check that the sandbox/container 82 // is still running, setting it to Stopped if not. 83 // 84 // This is inherently racy. 85 switch c.Status { 86 case Created: 87 if !c.IsSandboxRunning() { 88 // Sandbox no longer exists, so this container definitely does not exist. 89 c.changeStatus(Stopped) 90 } 91 case Running: 92 if err := c.SignalContainer(unix.Signal(0), false); err != nil { 93 c.changeStatus(Stopped) 94 } 95 } 96 } 97 98 return c, nil 99 } 100 101 // List returns all container ids in the given root directory. 102 func List(rootDir string) ([]FullID, error) { 103 log.Debugf("List containers %q", rootDir) 104 return listMatch(rootDir, FullID{}) 105 } 106 107 // listMatch returns all container ids that match the provided id. 108 func listMatch(rootDir string, id FullID) ([]FullID, error) { 109 id.SandboxID += "*" 110 id.ContainerID += "*" 111 pattern := buildPath(rootDir, id, stateFileExtension) 112 list, err := filepath.Glob(pattern) 113 if err != nil { 114 return nil, err 115 } 116 var out []FullID 117 for _, path := range list { 118 id, err := parseFileName(filepath.Base(path)) 119 if err == nil { 120 out = append(out, id) 121 } 122 } 123 return out, nil 124 } 125 126 // loadSandbox loads all containers that belong to the sandbox with the given 127 // ID. 128 func loadSandbox(rootDir, id string) ([]*Container, error) { 129 cids, err := listMatch(rootDir, FullID{SandboxID: id}) 130 if err != nil { 131 return nil, err 132 } 133 134 // Load the container metadata. 135 var containers []*Container 136 for _, cid := range cids { 137 container, err := Load(rootDir, cid, LoadOpts{Exact: true, SkipCheck: true}) 138 if err != nil { 139 // Container file may not exist if it raced with creation/deletion or 140 // directory was left behind. Load provides a snapshot in time, so it's 141 // fine to skip it. 142 if os.IsNotExist(err) { 143 continue 144 } 145 return nil, fmt.Errorf("loading sandbox %q, failed to load container %q: %v", id, cid, err) 146 } 147 containers = append(containers, container) 148 } 149 return containers, nil 150 } 151 152 func findContainerID(rootDir, partialID string) (FullID, error) { 153 // Check whether the id fully specifies an existing container. 154 pattern := buildPath(rootDir, FullID{SandboxID: "*", ContainerID: partialID + "*"}, stateFileExtension) 155 list, err := filepath.Glob(pattern) 156 if err != nil { 157 return FullID{}, err 158 } 159 switch len(list) { 160 case 0: 161 return FullID{}, os.ErrNotExist 162 case 1: 163 return parseFileName(filepath.Base(list[0])) 164 } 165 166 // Now see whether id could be an abbreviation of exactly 1 of the 167 // container ids. If id is ambiguous (it could match more than 1 168 // container), it is an error. 169 ids, err := List(rootDir) 170 if err != nil { 171 return FullID{}, err 172 } 173 var rv *FullID 174 for _, id := range ids { 175 if strings.HasPrefix(id.ContainerID, partialID) { 176 if rv != nil { 177 return FullID{}, fmt.Errorf("id %q is ambiguous and could refer to multiple containers: %q, %q", partialID, rv, id) 178 } 179 rv = &id 180 } 181 } 182 if rv == nil { 183 return FullID{}, os.ErrNotExist 184 } 185 log.Debugf("abbreviated id %q resolves to full id %v", partialID, *rv) 186 return *rv, nil 187 } 188 189 func parseFileName(name string) (FullID, error) { 190 re := regexp.MustCompile(`([\w+-\.]+)_sandbox:([\w+-\.]+)\.` + stateFileExtension) 191 groups := re.FindStringSubmatch(name) 192 if len(groups) != 3 { 193 return FullID{}, fmt.Errorf("invalid state file name format: %q", name) 194 } 195 id := FullID{ 196 SandboxID: groups[2], 197 ContainerID: groups[1], 198 } 199 if err := id.validate(); err != nil { 200 return FullID{}, fmt.Errorf("invalid state file name %q: %w", name, err) 201 } 202 return id, nil 203 } 204 205 // FullID combines sandbox and container ID to identify a container. Sandbox ID 206 // is used to allow all containers for a given sandbox to be loaded by matching 207 // sandbox ID in the file name. 208 type FullID struct { 209 SandboxID string `json:"sandboxId"` 210 ContainerID string `json:"containerId"` 211 } 212 213 func (f *FullID) String() string { 214 return f.SandboxID + "/" + f.ContainerID 215 } 216 217 func (f *FullID) validate() error { 218 if err := validateID(f.SandboxID); err != nil { 219 return err 220 } 221 return validateID(f.ContainerID) 222 } 223 224 // StateFile handles load from/save to container state safely from multiple 225 // processes. It uses a lock file to provide synchronization between operations. 226 // 227 // The lock file is located at: "${s.RootDir}/${containerd-id}_sand:{sandbox-id}.lock". 228 // The state file is located at: "${s.RootDir}/${containerd-id}_sand:{sandbox-id}.state". 229 type StateFile struct { 230 // RootDir is the directory containing the container metadata file. 231 RootDir string `json:"rootDir"` 232 233 // ID is the sandbox+container ID. 234 ID FullID `json:"id"` 235 236 // 237 // Fields below this line are not saved in the state file and will not 238 // be preserved across commands. 239 // 240 241 once sync.Once 242 flock *flock.Flock 243 } 244 245 // lock globally locks all locking operations for the container. 246 func (s *StateFile) lock() error { 247 s.once.Do(func() { 248 s.flock = flock.New(s.lockPath()) 249 }) 250 251 if err := s.flock.Lock(); err != nil { 252 return fmt.Errorf("acquiring lock on %q: %v", s.flock, err) 253 } 254 return nil 255 } 256 257 // lockForNew acquires the lock and checks if the state file doesn't exist. This 258 // is done to ensure that more than one creation didn't race to create 259 // containers with the same ID. 260 func (s *StateFile) lockForNew() error { 261 if err := s.lock(); err != nil { 262 return err 263 } 264 265 // Checks if the container already exists by looking for the metadata file. 266 if _, err := os.Stat(s.statePath()); err == nil { 267 s.unlock() 268 return fmt.Errorf("container already exists") 269 } else if !os.IsNotExist(err) { 270 s.unlock() 271 return fmt.Errorf("looking for existing container: %v", err) 272 } 273 return nil 274 } 275 276 // unlock globally unlocks all locking operations for the container. 277 func (s *StateFile) unlock() error { 278 if !s.flock.Locked() { 279 panic("unlock called without lock held") 280 } 281 282 if err := s.flock.Unlock(); err != nil { 283 log.Warningf("Error to release lock on %q: %v", s.flock, err) 284 return fmt.Errorf("releasing lock on %q: %v", s.flock, err) 285 } 286 return nil 287 } 288 289 // saveLocked saves 'v' to the state file. 290 // 291 // Preconditions: lock() must been called before. 292 func (s *StateFile) saveLocked(v interface{}) error { 293 if !s.flock.Locked() { 294 panic("saveLocked called without lock held") 295 } 296 297 meta, err := json.Marshal(v) 298 if err != nil { 299 return err 300 } 301 if err := ioutil.WriteFile(s.statePath(), meta, 0640); err != nil { 302 return fmt.Errorf("writing json file: %v", err) 303 } 304 return nil 305 } 306 307 func (s *StateFile) load(v interface{}) error { 308 if err := s.lock(); err != nil { 309 return err 310 } 311 defer s.unlock() 312 313 metaBytes, err := ioutil.ReadFile(s.statePath()) 314 if err != nil { 315 return err 316 } 317 return json.Unmarshal(metaBytes, &v) 318 } 319 320 func (s *StateFile) close() error { 321 if s.flock == nil { 322 return nil 323 } 324 if s.flock.Locked() { 325 panic("Closing locked file") 326 } 327 return s.flock.Close() 328 } 329 330 func buildPath(rootDir string, id FullID, extension string) string { 331 // Note: "_" and ":" are not valid in IDs. 332 name := fmt.Sprintf("%s_sandbox:%s.%s", id.ContainerID, id.SandboxID, extension) 333 return filepath.Join(rootDir, name) 334 } 335 336 // statePath is the full path to the state file. 337 func (s *StateFile) statePath() string { 338 return buildPath(s.RootDir, s.ID, stateFileExtension) 339 } 340 341 // lockPath is the full path to the lock file. 342 func (s *StateFile) lockPath() string { 343 return buildPath(s.RootDir, s.ID, "lock") 344 } 345 346 // destroy deletes all state created by the stateFile. It may be called with the 347 // lock file held. In that case, the lock file must still be unlocked and 348 // properly closed after destroy returns. 349 func (s *StateFile) destroy() error { 350 if err := os.Remove(s.statePath()); err != nil && !os.IsNotExist(err) { 351 return err 352 } 353 if err := os.Remove(s.lockPath()); err != nil && !os.IsNotExist(err) { 354 return err 355 } 356 return nil 357 }