// Copyright 2019 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package container import ( "encoding/json" "fmt" "io/ioutil" "os" "path/filepath" "github.com/gofrs/flock" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/sync" ) const stateFileExtension = ".state" // StateFile handles load from/save to container state safely from multiple // processes. It uses a lock file to provide synchronization between operations. // // The lock file is located at: "${s.RootDir}/${s.ID}.lock". // The state file is located at: "${s.RootDir}/${s.ID}.state". type StateFile struct { // RootDir is the directory containing the container metadata file. RootDir string `json:"rootDir"` // ID is the container ID. ID string `json:"id"` // // Fields below this line are not saved in the state file and will not // be preserved across commands. // once sync.Once flock *flock.Flock } // List returns all container ids in the given root directory. func List(rootDir string) ([]string, error) { log.Debugf("List containers %q", rootDir) list, err := filepath.Glob(filepath.Join(rootDir, "*"+stateFileExtension)) if err != nil { return nil, err } var out []string for _, path := range list { // Filter out files that do no belong to a container. fileName := filepath.Base(path) if len(fileName) < len(stateFileExtension) { panic(fmt.Sprintf("invalid file match %q", path)) } // Remove the extension. cid := fileName[:len(fileName)-len(stateFileExtension)] if validateID(cid) == nil { out = append(out, cid) } } return out, nil } // lock globally locks all locking operations for the container. func (s *StateFile) lock() error { s.once.Do(func() { s.flock = flock.NewFlock(s.lockPath()) }) if err := s.flock.Lock(); err != nil { return fmt.Errorf("acquiring lock on %q: %v", s.flock, err) } return nil } // lockForNew acquires the lock and checks if the state file doesn't exist. This // is done to ensure that more than one creation didn't race to create // containers with the same ID. func (s *StateFile) lockForNew() error { if err := s.lock(); err != nil { return err } // Checks if the container already exists by looking for the metadata file. if _, err := os.Stat(s.statePath()); err == nil { s.unlock() return fmt.Errorf("container already exists") } else if !os.IsNotExist(err) { s.unlock() return fmt.Errorf("looking for existing container: %v", err) } return nil } // unlock globally unlocks all locking operations for the container. func (s *StateFile) unlock() error { if !s.flock.Locked() { panic("unlock called without lock held") } if err := s.flock.Unlock(); err != nil { log.Warningf("Error to release lock on %q: %v", s.flock, err) return fmt.Errorf("releasing lock on %q: %v", s.flock, err) } return nil } // saveLocked saves 'v' to the state file. // // Preconditions: lock() must been called before. func (s *StateFile) saveLocked(v interface{}) error { if !s.flock.Locked() { panic("saveLocked called without lock held") } meta, err := json.Marshal(v) if err != nil { return err } if err := ioutil.WriteFile(s.statePath(), meta, 0640); err != nil { return fmt.Errorf("writing json file: %v", err) } return nil } func (s *StateFile) load(v interface{}) error { if err := s.lock(); err != nil { return err } defer s.unlock() metaBytes, err := ioutil.ReadFile(s.statePath()) if err != nil { return err } return json.Unmarshal(metaBytes, &v) } func (s *StateFile) close() error { if s.flock == nil { return nil } if s.flock.Locked() { panic("Closing locked file") } return s.flock.Close() } func buildStatePath(rootDir, id string) string { return filepath.Join(rootDir, id+stateFileExtension) } // statePath is the full path to the state file. func (s *StateFile) statePath() string { return buildStatePath(s.RootDir, s.ID) } // lockPath is the full path to the lock file. func (s *StateFile) lockPath() string { return filepath.Join(s.RootDir, s.ID+".lock") } // destroy deletes all state created by the stateFile. It may be called with the // lock file held. In that case, the lock file must still be unlocked and // properly closed after destroy returns. func (s *StateFile) destroy() error { if err := os.Remove(s.statePath()); err != nil && !os.IsNotExist(err) { return err } if err := os.Remove(s.lockPath()); err != nil && !os.IsNotExist(err) { return err } return nil }