github.com/kata-containers/runtime@v0.0.0-20210505125100-04f29832a923/virtcontainers/store/filesystem_backend.go (about)

     1  // Copyright (c) 2019 Intel Corporation
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  //
     5  
     6  package store
     7  
     8  import (
     9  	"context"
    10  	"encoding/json"
    11  	"fmt"
    12  	"io/ioutil"
    13  	"os"
    14  	"path/filepath"
    15  	"syscall"
    16  
    17  	"github.com/kata-containers/runtime/virtcontainers/pkg/rootless"
    18  	"github.com/kata-containers/runtime/virtcontainers/pkg/uuid"
    19  	opentracing "github.com/opentracing/opentracing-go"
    20  	"github.com/sirupsen/logrus"
    21  )
    22  
    23  const (
    24  	// ConfigurationFile is the file name used for every JSON sandbox configuration.
    25  	ConfigurationFile string = "config.json"
    26  
    27  	// StateFile is the file name storing a sandbox state.
    28  	StateFile = "state.json"
    29  
    30  	// NetworkFile is the file name storing a sandbox network.
    31  	NetworkFile = "network.json"
    32  
    33  	// HypervisorFile is the file name storing a hypervisor's state.
    34  	HypervisorFile = "hypervisor.json"
    35  
    36  	// AgentFile is the file name storing an agent's state.
    37  	AgentFile = "agent.json"
    38  
    39  	// ProcessFile is the file name storing a container process.
    40  	ProcessFile = "process.json"
    41  
    42  	// LockFile is the file name locking the usage of a resource.
    43  	LockFile = "lock"
    44  
    45  	// MountsFile is the file name storing a container's mount points.
    46  	MountsFile = "mounts.json"
    47  
    48  	// DevicesFile is the file name storing a container's devices.
    49  	DevicesFile = "devices.json"
    50  
    51  	// uuidFile is the file name storing a guest VM uuid state.
    52  	uuidFile = "uuid.json"
    53  )
    54  
    55  // DirMode is the permission bits used for creating a directory
    56  const DirMode = os.FileMode(0750) | os.ModeDir
    57  
    58  // StoragePathSuffix is the suffix used for all storage paths
    59  //
    60  // Note: this very brief path represents "virtcontainers". It is as
    61  // terse as possible to minimise path length.
    62  const StoragePathSuffix = "vc"
    63  
    64  // SandboxPathSuffix is the suffix used for sandbox storage
    65  const SandboxPathSuffix = "sbs"
    66  
    67  // VMPathSuffix is the suffix used for guest VMs.
    68  const VMPathSuffix = "vm"
    69  
    70  // UUIDPathSuffix is the suffix used for uuid storage
    71  const UUIDPathSuffix = "uuid"
    72  
    73  // ConfigStoragePath is the sandbox configuration directory.
    74  // It will contain one config.json file for each created sandbox.
    75  // The function is declared this way for mocking in unit tests
    76  var ConfigStoragePath = func() string {
    77  	path := filepath.Join("/var/lib", StoragePathSuffix, SandboxPathSuffix)
    78  	if rootless.IsRootless() {
    79  		return filepath.Join(rootless.GetRootlessDir(), path)
    80  	}
    81  	return path
    82  }
    83  
    84  // RunStoragePath is the sandbox runtime directory.
    85  // It will contain one state.json and one lock file for each created sandbox.
    86  // The function is declared this way for mocking in unit tests
    87  var RunStoragePath = func() string {
    88  	path := filepath.Join("/run", StoragePathSuffix, SandboxPathSuffix)
    89  	if rootless.IsRootless() {
    90  		return filepath.Join(rootless.GetRootlessDir(), path)
    91  	}
    92  	return path
    93  }
    94  
    95  // RunVMStoragePath is the vm directory.
    96  // It will contain all guest vm sockets and shared mountpoints.
    97  // The function is declared this way for mocking in unit tests
    98  var RunVMStoragePath = func() string {
    99  	path := filepath.Join("/run", StoragePathSuffix, VMPathSuffix)
   100  	if rootless.IsRootless() {
   101  		return filepath.Join(rootless.GetRootlessDir(), path)
   102  	}
   103  	return path
   104  }
   105  
   106  // VMUUIDStoragePath is the uuid directory.
   107  // It will contain all uuid info used by guest vm.
   108  var VMUUIDStoragePath = func() string {
   109  	path := filepath.Join("/var/lib", StoragePathSuffix, UUIDPathSuffix)
   110  	if rootless.IsRootless() {
   111  		return filepath.Join(rootless.GetRootlessDir(), path)
   112  	}
   113  	return path
   114  
   115  }
   116  
   117  func itemToFile(item Item) (string, error) {
   118  	switch item {
   119  	case Configuration:
   120  		return ConfigurationFile, nil
   121  	case State:
   122  		return StateFile, nil
   123  	case Network:
   124  		return NetworkFile, nil
   125  	case Hypervisor:
   126  		return HypervisorFile, nil
   127  	case Agent:
   128  		return AgentFile, nil
   129  	case Process:
   130  		return ProcessFile, nil
   131  	case Lock:
   132  		return LockFile, nil
   133  	case Mounts:
   134  		return MountsFile, nil
   135  	case Devices, DeviceIDs:
   136  		return DevicesFile, nil
   137  	case UUID:
   138  		return uuidFile, nil
   139  	}
   140  
   141  	return "", fmt.Errorf("Unknown item %s", item)
   142  }
   143  
   144  type filesystem struct {
   145  	ctx context.Context
   146  
   147  	path    string
   148  	rawPath string
   149  
   150  	lockTokens map[string]*os.File
   151  }
   152  
   153  // Logger returns a logrus logger appropriate for logging Store filesystem messages
   154  func (f *filesystem) logger() *logrus.Entry {
   155  	return storeLog.WithFields(logrus.Fields{
   156  		"subsystem": "store",
   157  		"backend":   "filesystem",
   158  		"path":      f.path,
   159  	})
   160  }
   161  
   162  func (f *filesystem) trace(name string) (opentracing.Span, context.Context) {
   163  	if f.ctx == nil {
   164  		f.logger().WithField("type", "bug").Error("trace called before context set")
   165  		f.ctx = context.Background()
   166  	}
   167  
   168  	span, ctx := opentracing.StartSpanFromContext(f.ctx, name)
   169  
   170  	span.SetTag("subsystem", "store")
   171  	span.SetTag("type", "filesystem")
   172  	span.SetTag("path", f.path)
   173  
   174  	return span, ctx
   175  }
   176  
   177  func (f *filesystem) itemToPath(item Item) (string, error) {
   178  	fileName, err := itemToFile(item)
   179  	if err != nil {
   180  		return "", err
   181  	}
   182  
   183  	return filepath.Join(f.path, fileName), nil
   184  }
   185  
   186  func (f *filesystem) initialize() error {
   187  	_, err := os.Stat(f.path)
   188  	if err == nil {
   189  		return nil
   190  	}
   191  
   192  	// Our root path does not exist, we need to create the initial layout:
   193  	// The root directory, a lock file and a raw files directory.
   194  
   195  	// Root directory
   196  	f.logger().WithField("path", f.path).Debugf("Creating root directory")
   197  	if err := os.MkdirAll(f.path, DirMode); err != nil {
   198  		return err
   199  	}
   200  
   201  	// Raw directory
   202  	f.logger().WithField("path", f.rawPath).Debugf("Creating raw directory")
   203  	if err := os.MkdirAll(f.rawPath, DirMode); err != nil {
   204  		return err
   205  	}
   206  
   207  	// Lock
   208  	lockPath := filepath.Join(f.path, LockFile)
   209  
   210  	lockFile, err := os.Create(lockPath)
   211  	if err != nil {
   212  		f.delete()
   213  		return err
   214  	}
   215  	lockFile.Close()
   216  
   217  	return nil
   218  }
   219  
   220  func (f *filesystem) new(ctx context.Context, path string, host string) error {
   221  	f.ctx = ctx
   222  	f.path = path
   223  	f.rawPath = filepath.Join(f.path, "raw")
   224  	f.lockTokens = make(map[string]*os.File)
   225  
   226  	f.logger().Debugf("New filesystem store backend for %s", path)
   227  
   228  	return f.initialize()
   229  }
   230  
   231  func (f *filesystem) delete() error {
   232  	f.logger().WithField("path", f.path).Debugf("Deleting files")
   233  	return os.RemoveAll(f.path)
   234  }
   235  
   236  func (f *filesystem) load(item Item, data interface{}) error {
   237  	span, _ := f.trace("load")
   238  	defer span.Finish()
   239  
   240  	span.SetTag("item", item)
   241  
   242  	filePath, err := f.itemToPath(item)
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	fileData, err := ioutil.ReadFile(filePath)
   248  	if err != nil {
   249  		return err
   250  	}
   251  
   252  	if err := json.Unmarshal(fileData, data); err != nil {
   253  		return err
   254  	}
   255  
   256  	return nil
   257  }
   258  
   259  func (f *filesystem) store(item Item, data interface{}) error {
   260  	span, _ := f.trace("store")
   261  	defer span.Finish()
   262  
   263  	span.SetTag("item", item)
   264  
   265  	filePath, err := f.itemToPath(item)
   266  	if err != nil {
   267  		return err
   268  	}
   269  
   270  	file, err := os.Create(filePath)
   271  	if err != nil {
   272  		return err
   273  	}
   274  	defer file.Close()
   275  
   276  	jsonOut, err := json.Marshal(data)
   277  	if err != nil {
   278  		return fmt.Errorf("Could not marshall data: %s", err)
   279  	}
   280  	file.Write(jsonOut)
   281  
   282  	return nil
   283  }
   284  
   285  func (f *filesystem) raw(id string) (string, error) {
   286  	span, _ := f.trace("raw")
   287  	defer span.Finish()
   288  
   289  	span.SetTag("id", id)
   290  
   291  	// We must use the item ID.
   292  	if id != "" {
   293  		filePath := filepath.Join(f.rawPath, id)
   294  		file, err := os.Create(filePath)
   295  		if err != nil {
   296  			return "", err
   297  		}
   298  
   299  		return filesystemScheme + "://" + file.Name(), nil
   300  	}
   301  
   302  	// Generate a random temporary file.
   303  	file, err := ioutil.TempFile(f.rawPath, "raw-")
   304  	if err != nil {
   305  		return "", err
   306  	}
   307  	defer file.Close()
   308  
   309  	return filesystemScheme + "://" + file.Name(), nil
   310  }
   311  
   312  func (f *filesystem) lock(item Item, exclusive bool) (string, error) {
   313  	itemPath, err := f.itemToPath(item)
   314  	if err != nil {
   315  		return "", err
   316  	}
   317  
   318  	itemFile, err := os.Open(itemPath)
   319  	if err != nil {
   320  		return "", err
   321  	}
   322  
   323  	var lockType int
   324  	if exclusive {
   325  		lockType = syscall.LOCK_EX
   326  	} else {
   327  		lockType = syscall.LOCK_SH
   328  	}
   329  
   330  	if err := syscall.Flock(int(itemFile.Fd()), lockType); err != nil {
   331  		itemFile.Close()
   332  		return "", err
   333  	}
   334  
   335  	token := uuid.Generate().String()
   336  	f.lockTokens[token] = itemFile
   337  
   338  	return token, nil
   339  }
   340  
   341  func (f *filesystem) unlock(item Item, token string) error {
   342  	itemFile := f.lockTokens[token]
   343  	if itemFile == nil {
   344  		return fmt.Errorf("No lock for token %s", token)
   345  	}
   346  
   347  	if err := syscall.Flock(int(itemFile.Fd()), syscall.LOCK_UN); err != nil {
   348  		return err
   349  	}
   350  
   351  	itemFile.Close()
   352  	delete(f.lockTokens, token)
   353  
   354  	return nil
   355  }