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  }