automation-tests/storage/store.go

3968 lines
123 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package storage
import (
_ "embed"
"encoding/base64"
"errors"
"fmt"
"io"
"maps"
"os"
"path/filepath"
"reflect"
"slices"
"strings"
"sync"
"syscall"
"time"
// register all of the built-in drivers
_ "github.com/containers/storage/drivers/register"
"golang.org/x/sync/errgroup"
drivers "github.com/containers/storage/drivers"
"github.com/containers/storage/internal/dedup"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/directory"
"github.com/containers/storage/pkg/idtools"
"github.com/containers/storage/pkg/ioutils"
"github.com/containers/storage/pkg/lockfile"
"github.com/containers/storage/pkg/parsers"
"github.com/containers/storage/pkg/stringutils"
"github.com/containers/storage/pkg/system"
"github.com/containers/storage/types"
digest "github.com/opencontainers/go-digest"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/sirupsen/logrus"
)
type updateNameOperation int
const (
setNames updateNameOperation = iota
addNames
removeNames
)
const (
volatileFlag = "Volatile"
mountLabelFlag = "MountLabel"
processLabelFlag = "ProcessLabel"
mountOptsFlag = "MountOpts"
)
var (
stores []*store
storesLock sync.Mutex
)
// roMetadataStore wraps a method for reading metadata associated with an ID.
type roMetadataStore interface {
// Metadata reads metadata associated with an item with the specified ID.
Metadata(id string) (string, error)
}
// rwMetadataStore wraps a method for setting metadata associated with an ID.
type rwMetadataStore interface {
// SetMetadata updates the metadata associated with the item with the specified ID.
SetMetadata(id, metadata string) error
}
// metadataStore wraps up methods for getting and setting metadata associated with IDs.
type metadataStore interface {
roMetadataStore
rwMetadataStore
}
// ApplyStagedLayerOptions contains options to pass to ApplyStagedLayer
type ApplyStagedLayerOptions struct {
ID string // Mandatory
ParentLayer string // Optional
Names []string // Optional
MountLabel string // Optional
Writeable bool // Optional
LayerOptions *LayerOptions // Optional
DiffOutput *drivers.DriverWithDifferOutput // Mandatory
DiffOptions *drivers.ApplyDiffWithDifferOpts // Mandatory
}
// MultiListOptions contains options to pass to MultiList
type MultiListOptions struct {
Images bool // if true, Images will be listed in the result
Layers bool // if true, layers will be listed in the result
Containers bool // if true, containers will be listed in the result
}
// MultiListResult contains slices of Images, Layers or Containers listed by MultiList method
type MultiListResult struct {
Images []Image
Layers []Layer
Containers []Container
}
// An roBigDataStore wraps up the read-only big-data related methods of the
// various types of file-based lookaside stores that we implement.
type roBigDataStore interface {
// BigData retrieves a (potentially large) piece of data associated with
// this ID, if it has previously been set.
BigData(id, key string) ([]byte, error)
// BigDataSize retrieves the size of a (potentially large) piece of
// data associated with this ID, if it has previously been set.
BigDataSize(id, key string) (int64, error)
// BigDataDigest retrieves the digest of a (potentially large) piece of
// data associated with this ID, if it has previously been set.
BigDataDigest(id, key string) (digest.Digest, error)
// BigDataNames() returns a list of the names of previously-stored pieces of
// data.
BigDataNames(id string) ([]string, error)
}
// A rwImageBigDataStore wraps up how we store big-data associated with images.
type rwImageBigDataStore interface {
// SetBigData stores a (potentially large) piece of data associated
// with this ID.
// Pass github.com/containers/image/manifest.Digest as digestManifest
// to allow ByDigest to find images by their correct digests.
SetBigData(id, key string, data []byte, digestManifest func([]byte) (digest.Digest, error)) error
}
// A containerBigDataStore wraps up how we store big-data associated with containers.
type containerBigDataStore interface {
roBigDataStore
// SetBigData stores a (potentially large) piece of data associated
// with this ID.
SetBigData(id, key string, data []byte) error
}
// A roLayerBigDataStore wraps up how we store RO big-data associated with layers.
type roLayerBigDataStore interface {
// SetBigData stores a (potentially large) piece of data associated
// with this ID.
BigData(id, key string) (io.ReadCloser, error)
// BigDataNames() returns a list of the names of previously-stored pieces of
// data.
BigDataNames(id string) ([]string, error)
}
// A rwLayerBigDataStore wraps up how we store big-data associated with layers.
type rwLayerBigDataStore interface {
// SetBigData stores a (potentially large) piece of data associated
// with this ID.
SetBigData(id, key string, data io.Reader) error
}
// A flaggableStore can have flags set and cleared on items which it manages.
type flaggableStore interface {
// ClearFlag removes a named flag from an item in the store.
ClearFlag(id string, flag string) error
// SetFlag sets a named flag and its value on an item in the store.
SetFlag(id string, flag string, value any) error
}
type StoreOptions = types.StoreOptions
type DedupHashMethod = dedup.DedupHashMethod
const (
DedupHashInvalid = dedup.DedupHashInvalid
DedupHashCRC = dedup.DedupHashCRC
DedupHashFileSize = dedup.DedupHashFileSize
DedupHashSHA256 = dedup.DedupHashSHA256
)
type (
DedupOptions = dedup.DedupOptions
DedupResult = dedup.DedupResult
)
// DedupArgs is used to pass arguments to the Dedup command.
type DedupArgs struct {
// Options that are passed directly to the internal/dedup.DedupDirs function.
Options DedupOptions
}
// Store wraps up the various types of file-based stores that we use into a
// singleton object that initializes and manages them all together.
type Store interface {
// RunRoot, GraphRoot, GraphDriverName, and GraphOptions retrieve
// settings that were passed to GetStore() when the object was created.
RunRoot() string
GraphRoot() string
ImageStore() string
TransientStore() bool
GraphDriverName() string
GraphOptions() []string
PullOptions() map[string]string
UIDMap() []idtools.IDMap
GIDMap() []idtools.IDMap
// GraphDriver obtains and returns a handle to the graph Driver object used
// by the Store.
GraphDriver() (drivers.Driver, error)
// CreateLayer creates a new layer in the underlying storage driver,
// optionally having the specified ID (one will be assigned if none is
// specified), with the specified layer (or no layer) as its parent,
// and with optional names. (The writeable flag is ignored.)
CreateLayer(id, parent string, names []string, mountLabel string, writeable bool, options *LayerOptions) (*Layer, error)
// PutLayer combines the functions of CreateLayer and ApplyDiff,
// marking the layer for automatic removal if applying the diff fails
// for any reason.
//
// Note that we do some of this work in a child process. The calling
// process's main() function needs to import our pkg/reexec package and
// should begin with something like this in order to allow us to
// properly start that child process:
// if reexec.Init() {
// return
// }
PutLayer(id, parent string, names []string, mountLabel string, writeable bool, options *LayerOptions, diff io.Reader) (*Layer, int64, error)
// CreateImage creates a new image, optionally with the specified ID
// (one will be assigned if none is specified), with optional names,
// referring to a specified image, and with optional metadata. An
// image is a record which associates the ID of a layer with a
// additional bookkeeping information which the library stores for the
// convenience of its caller.
CreateImage(id string, names []string, layer, metadata string, options *ImageOptions) (*Image, error)
// CreateContainer creates a new container, optionally with the
// specified ID (one will be assigned if none is specified), with
// optional names, using the specified image's top layer as the basis
// for the container's layer, and assigning the specified ID to that
// layer (one will be created if none is specified). A container is a
// layer which is associated with additional bookkeeping information
// which the library stores for the convenience of its caller.
CreateContainer(id string, names []string, image, layer, metadata string, options *ContainerOptions) (*Container, error)
// Metadata retrieves the metadata which is associated with a layer,
// image, or container (whichever the passed-in ID refers to).
Metadata(id string) (string, error)
// SetMetadata updates the metadata which is associated with a layer,
// image, or container (whichever the passed-in ID refers to) to match
// the specified value. The metadata value can be retrieved at any
// time using Metadata, or using Layer, Image, or Container and reading
// the object directly.
SetMetadata(id, metadata string) error
// Exists checks if there is a layer, image, or container which has the
// passed-in ID or name.
Exists(id string) bool
// Status asks for a status report, in the form of key-value pairs,
// from the underlying storage driver. The contents vary from driver
// to driver.
Status() ([][2]string, error)
// Delete removes the layer, image, or container which has the
// passed-in ID or name. Note that no safety checks are performed, so
// this can leave images with references to layers which do not exist,
// and layers with references to parents which no longer exist.
Delete(id string) error
// DeleteLayer attempts to remove the specified layer. If the layer is the
// parent of any other layer, or is referred to by any images, it will return
// an error.
DeleteLayer(id string) error
// DeleteImage removes the specified image if it is not referred to by
// any containers. If its top layer is then no longer referred to by
// any other images and is not the parent of any other layers, its top
// layer will be removed. If that layer's parent is no longer referred
// to by any other images and is not the parent of any other layers,
// then it, too, will be removed. This procedure will be repeated
// until a layer which should not be removed, or the base layer, is
// reached, at which point the list of removed layers is returned. If
// the commit argument is false, the image and layers are not removed,
// but the list of layers which would be removed is still returned.
DeleteImage(id string, commit bool) (layers []string, err error)
// DeleteContainer removes the specified container and its layer. If
// there is no matching container, or if the container exists but its
// layer does not, an error will be returned.
DeleteContainer(id string) error
// Wipe removes all known layers, images, and containers.
Wipe() error
// MountImage mounts an image to temp directory and returns the mount point.
// MountImage allows caller to mount an image. Images will always
// be mounted read/only
MountImage(id string, mountOptions []string, mountLabel string) (string, error)
// Unmount attempts to unmount an image, given an ID.
// Returns whether or not the layer is still mounted.
// WARNING: The return value may already be obsolete by the time it is available
// to the caller, so it can be used for heuristic sanity checks at best. It should almost always be ignored.
UnmountImage(id string, force bool) (bool, error)
// Mount attempts to mount a layer, image, or container for access, and
// returns the pathname if it succeeds.
// Note if the mountLabel == "", the default label for the container
// will be used.
//
// Note that we do some of this work in a child process. The calling
// process's main() function needs to import our pkg/reexec package and
// should begin with something like this in order to allow us to
// properly start that child process:
// if reexec.Init() {
// return
// }
Mount(id, mountLabel string) (string, error)
// Unmount attempts to unmount a layer, image, or container, given an ID, a
// name, or a mount path. Returns whether or not the layer is still mounted.
// WARNING: The return value may already be obsolete by the time it is available
// to the caller, so it can be used for heuristic sanity checks at best. It should almost always be ignored.
Unmount(id string, force bool) (bool, error)
// Mounted returns number of times the layer has been mounted.
//
// WARNING: This value might already be obsolete by the time it is returned;
// In situations where concurrent mount/unmount attempts can happen, this field
// should not be used for any decisions, maybe apart from heuristic user warnings.
Mounted(id string) (int, error)
// Changes returns a summary of the changes which would need to be made
// to one layer to make its contents the same as a second layer. If
// the first layer is not specified, the second layer's parent is
// assumed. Each Change structure contains a Path relative to the
// layer's root directory, and a Kind which is either ChangeAdd,
// ChangeModify, or ChangeDelete.
Changes(from, to string) ([]archive.Change, error)
// DiffSize returns a count of the size of the tarstream which would
// specify the changes returned by Changes.
DiffSize(from, to string) (int64, error)
// Diff returns the tarstream which would specify the changes returned
// by Changes. If options are passed in, they can override default
// behaviors.
Diff(from, to string, options *DiffOptions) (io.ReadCloser, error)
// ApplyDiff applies a tarstream to a layer. Information about the
// tarstream is cached with the layer. Typically, a layer which is
// populated using a tarstream will be expected to not be modified in
// any other way, either before or after the diff is applied.
//
// Note that we do some of this work in a child process. The calling
// process's main() function needs to import our pkg/reexec package and
// should begin with something like this in order to allow us to
// properly start that child process:
// if reexec.Init() {
// return
// }
ApplyDiff(to string, diff io.Reader) (int64, error)
// PrepareStagedLayer applies a diff to a layer.
// It is the caller responsibility to clean the staging directory if it is not
// successfully applied with ApplyStagedLayer.
// The caller must ensure [Store.ApplyStagedLayer] or [Store.CleanupStagedLayer] is called eventually
// with the returned [drivers.DriverWithDifferOutput] object.
PrepareStagedLayer(options *drivers.ApplyDiffWithDifferOpts, differ drivers.Differ) (*drivers.DriverWithDifferOutput, error)
// ApplyStagedLayer combines the functions of creating a layer and using the staging
// directory to populate it.
// It marks the layer for automatic removal if applying the diff fails for any reason.
ApplyStagedLayer(args ApplyStagedLayerOptions) (*Layer, error)
// CleanupStagedLayer cleanups the staging directory. It can be used to cleanup the staging directory on errors
CleanupStagedLayer(diffOutput *drivers.DriverWithDifferOutput) error
// DifferTarget gets the path to the differ target.
DifferTarget(id string) (string, error)
// LayersByCompressedDigest returns a slice of the layers with the
// specified compressed digest value recorded for them.
LayersByCompressedDigest(d digest.Digest) ([]Layer, error)
// LayersByUncompressedDigest returns a slice of the layers with the
// specified uncompressed digest value recorded for them.
LayersByUncompressedDigest(d digest.Digest) ([]Layer, error)
// LayersByTOCDigest returns a slice of the layers with the
// specified TOC digest value recorded for them.
LayersByTOCDigest(d digest.Digest) ([]Layer, error)
// LayerSize returns a cached approximation of the layer's size, or -1
// if we don't have a value on hand.
LayerSize(id string) (int64, error)
// LayerParentOwners returns the UIDs and GIDs of owners of parents of
// the layer's mountpoint for which the layer's UID and GID maps (if
// any are defined) don't contain corresponding IDs.
LayerParentOwners(id string) ([]int, []int, error)
// Layers returns a list of the currently known layers.
Layers() ([]Layer, error)
// Images returns a list of the currently known images.
Images() ([]Image, error)
// Containers returns a list of the currently known containers.
Containers() ([]Container, error)
// Names returns the list of names for a layer, image, or container.
Names(id string) ([]string, error)
// Free removes the store from the list of stores
Free()
// SetNames changes the list of names for a layer, image, or container.
// Duplicate names are removed from the list automatically.
// Deprecated: Prone to race conditions, suggested alternatives are `AddNames` and `RemoveNames`.
SetNames(id string, names []string) error
// AddNames adds the list of names for a layer, image, or container.
// Duplicate names are removed from the list automatically.
AddNames(id string, names []string) error
// RemoveNames removes the list of names for a layer, image, or container.
// Duplicate names are removed from the list automatically.
RemoveNames(id string, names []string) error
// ListImageBigData retrieves a list of the (possibly large) chunks of
// named data associated with an image.
ListImageBigData(id string) ([]string, error)
// ImageBigData retrieves a (possibly large) chunk of named data
// associated with an image.
ImageBigData(id, key string) ([]byte, error)
// ImageBigDataSize retrieves the size of a (possibly large) chunk
// of named data associated with an image.
ImageBigDataSize(id, key string) (int64, error)
// ImageBigDataDigest retrieves the digest of a (possibly large) chunk
// of named data associated with an image.
ImageBigDataDigest(id, key string) (digest.Digest, error)
// SetImageBigData stores a (possibly large) chunk of named data
// associated with an image. Pass
// github.com/containers/image/manifest.Digest as digestManifest to
// allow ImagesByDigest to find images by their correct digests.
SetImageBigData(id, key string, data []byte, digestManifest func([]byte) (digest.Digest, error)) error
// ImageDirectory returns a path of a directory which the caller can
// use to store data, specific to the image, which the library does not
// directly manage. The directory will be deleted when the image is
// deleted.
ImageDirectory(id string) (string, error)
// ImageRunDirectory returns a path of a directory which the caller can
// use to store data, specific to the image, which the library does not
// directly manage. The directory will be deleted when the host system
// is restarted.
ImageRunDirectory(id string) (string, error)
// ListLayerBigData retrieves a list of the (possibly large) chunks of
// named data associated with a layer.
ListLayerBigData(id string) ([]string, error)
// LayerBigData retrieves a (possibly large) chunk of named data
// associated with a layer.
LayerBigData(id, key string) (io.ReadCloser, error)
// SetLayerBigData stores a (possibly large) chunk of named data
// associated with a layer.
SetLayerBigData(id, key string, data io.Reader) error
// ImageSize computes the size of the image's layers and ancillary data.
ImageSize(id string) (int64, error)
// ListContainerBigData retrieves a list of the (possibly large) chunks of
// named data associated with a container.
ListContainerBigData(id string) ([]string, error)
// ContainerBigData retrieves a (possibly large) chunk of named data
// associated with a container.
ContainerBigData(id, key string) ([]byte, error)
// ContainerBigDataSize retrieves the size of a (possibly large)
// chunk of named data associated with a container.
ContainerBigDataSize(id, key string) (int64, error)
// ContainerBigDataDigest retrieves the digest of a (possibly large)
// chunk of named data associated with a container.
ContainerBigDataDigest(id, key string) (digest.Digest, error)
// SetContainerBigData stores a (possibly large) chunk of named data
// associated with a container.
SetContainerBigData(id, key string, data []byte) error
// ContainerSize computes the size of the container's layer and ancillary
// data. Warning: this is a potentially expensive operation.
ContainerSize(id string) (int64, error)
// Layer returns a specific layer.
Layer(id string) (*Layer, error)
// Image returns a specific image.
Image(id string) (*Image, error)
// ImagesByTopLayer returns a list of images which reference the specified
// layer as their top layer. They will have different IDs and names
// and may have different metadata, big data items, and flags.
ImagesByTopLayer(id string) ([]*Image, error)
// ImagesByDigest returns a list of images which contain a big data item
// named ImageDigestBigDataKey whose contents have the specified digest.
ImagesByDigest(d digest.Digest) ([]*Image, error)
// Container returns a specific container.
Container(id string) (*Container, error)
// ContainerByLayer returns a specific container based on its layer ID or
// name.
ContainerByLayer(id string) (*Container, error)
// ContainerDirectory returns a path of a directory which the caller
// can use to store data, specific to the container, which the library
// does not directly manage. The directory will be deleted when the
// container is deleted.
ContainerDirectory(id string) (string, error)
// SetContainerDirectoryFile is a convenience function which stores
// a piece of data in the specified file relative to the container's
// directory.
SetContainerDirectoryFile(id, file string, data []byte) error
// FromContainerDirectory is a convenience function which reads
// the contents of the specified file relative to the container's
// directory.
FromContainerDirectory(id, file string) ([]byte, error)
// ContainerRunDirectory returns a path of a directory which the
// caller can use to store data, specific to the container, which the
// library does not directly manage. The directory will be deleted
// when the host system is restarted.
ContainerRunDirectory(id string) (string, error)
// SetContainerRunDirectoryFile is a convenience function which stores
// a piece of data in the specified file relative to the container's
// run directory.
SetContainerRunDirectoryFile(id, file string, data []byte) error
// FromContainerRunDirectory is a convenience function which reads
// the contents of the specified file relative to the container's run
// directory.
FromContainerRunDirectory(id, file string) ([]byte, error)
// ContainerParentOwners returns the UIDs and GIDs of owners of parents
// of the container's layer's mountpoint for which the layer's UID and
// GID maps (if any are defined) don't contain corresponding IDs.
ContainerParentOwners(id string) ([]int, []int, error)
// Lookup returns the ID of a layer, image, or container with the specified
// name or ID.
Lookup(name string) (string, error)
// Shutdown attempts to free any kernel resources which are being used
// by the underlying driver. If "force" is true, any mounted (i.e., in
// use) layers are unmounted beforehand. If "force" is not true, then
// layers being in use is considered to be an error condition. A list
// of still-mounted layers is returned along with possible errors.
Shutdown(force bool) (layers []string, err error)
// Version returns version information, in the form of key-value pairs, from
// the storage package.
Version() ([][2]string, error)
// GetDigestLock returns digest-specific Locker.
GetDigestLock(digest.Digest) (Locker, error)
// LayerFromAdditionalLayerStore searches the additional layer store and returns an object
// which can create a layer with the specified TOC digest associated with the specified image
// reference. Note that this hasn't been stored to this store yet: the actual creation of
// a usable layer is done by calling the returned object's PutAs() method. After creating
// a layer, the caller must then call the object's Release() method to free any temporary
// resources which were allocated for the object by this method or the object's PutAs()
// method.
// This API is experimental and can be changed without bumping the major version number.
LookupAdditionalLayer(tocDigest digest.Digest, imageref string) (AdditionalLayer, error)
// Tries to clean up remainders of previous containers or layers that are not
// references in the json files. These can happen in the case of unclean
// shutdowns or regular restarts in transient store mode.
GarbageCollect() error
// Check returns a report of things that look wrong in the store.
Check(options *CheckOptions) (CheckReport, error)
// Repair attempts to remediate problems mentioned in the CheckReport,
// usually by deleting layers and images which are damaged. If the
// right options are set, it will remove containers as well.
Repair(report CheckReport, options *RepairOptions) []error
// MultiList returns a MultiListResult structure that contains layer, image, or container
// extracts according to the values in MultiListOptions.
// MultiList returns consistent values as of a single point in time.
// WARNING: The values may already be out of date by the time they are returned to the caller.
MultiList(MultiListOptions) (MultiListResult, error)
// Dedup deduplicates layers in the store.
Dedup(DedupArgs) (drivers.DedupResult, error)
}
// AdditionalLayer represents a layer that is contained in the additional layer store
// This API is experimental and can be changed without bumping the major version number.
type AdditionalLayer interface {
// PutAs creates layer based on this handler, using diff contents from the additional
// layer store.
PutAs(id, parent string, names []string) (*Layer, error)
// TOCDigest returns the digest of TOC of this layer. Returns "" if unknown.
TOCDigest() digest.Digest
// CompressedSize returns the compressed size of this layer
CompressedSize() int64
// Release tells the additional layer store that we don't use this handler.
Release()
}
type AutoUserNsOptions = types.AutoUserNsOptions
type IDMappingOptions = types.IDMappingOptions
// LayerOptions is used for passing options to a Store's CreateLayer() and PutLayer() methods.
type LayerOptions struct {
// IDMappingOptions specifies the type of ID mapping which should be
// used for this layer. If nothing is specified, the layer will
// inherit settings from its parent layer or, if it has no parent
// layer, the Store object.
types.IDMappingOptions
// TemplateLayer is the ID of a layer whose contents will be used to
// initialize this layer. If set, it should be a child of the layer
// which we want to use as the parent of the new layer.
TemplateLayer string
// OriginalDigest specifies a digest of the (possibly-compressed) tarstream (diff), if one is
// provided along with these LayerOptions, and reliably known by the caller.
// The digest might not be exactly the digest of the provided tarstream
// (e.g. the digest might be of a compressed representation, while providing
// an uncompressed one); in that case the caller is responsible for the two matching.
// Use the default "" if this fields is not applicable or the value is not known.
OriginalDigest digest.Digest
// OriginalSize specifies a size of the (possibly-compressed) tarstream corresponding
// to OriginalDigest.
// If the digest does not match the provided tarstream, OriginalSize must match OriginalDigest,
// not the tarstream.
// Use nil if not applicable or not known.
OriginalSize *int64
// UncompressedDigest specifies a digest of the uncompressed version (“DiffID”)
// of the tarstream (diff), if one is provided along with these LayerOptions,
// and reliably known by the caller.
// Use the default "" if this fields is not applicable or the value is not known.
UncompressedDigest digest.Digest
// True is the layer info can be treated as volatile
Volatile bool
// BigData is a set of items which should be stored with the layer.
BigData []LayerBigDataOption
// Flags is a set of named flags and their values to store with the layer.
// Currently these can only be set when the layer record is created, but that
// could change in the future.
Flags map[string]any
}
type LayerBigDataOption struct {
Key string
Data io.Reader
}
// ImageOptions is used for passing options to a Store's CreateImage() method.
type ImageOptions struct {
// CreationDate, if not zero, will override the default behavior of marking the image as having been
// created when CreateImage() was called, recording CreationDate instead.
CreationDate time.Time
// Digest is a hard-coded digest value that we can use to look up the image. It is optional.
Digest digest.Digest
// Digests is a list of digest values of the image's manifests, and
// possibly a manually-specified value, that we can use to locate the
// image. If Digest is set, its value is also in this list.
Digests []digest.Digest
// Metadata is caller-specified metadata associated with the layer.
Metadata string
// BigData is a set of items which should be stored with the image.
BigData []ImageBigDataOption
// NamesHistory is used for guessing for what this image was named when a container was created based
// on it, but it no longer has any names.
NamesHistory []string
// Flags is a set of named flags and their values to store with the image. Currently these can only
// be set when the image record is created, but that could change in the future.
Flags map[string]any
}
type ImageBigDataOption struct {
Key string
Data []byte
Digest digest.Digest
}
// ContainerOptions is used for passing options to a Store's CreateContainer() method.
type ContainerOptions struct {
// IDMappingOptions specifies the type of ID mapping which should be
// used for this container's layer. If nothing is specified, the
// container's layer will inherit settings from the image's top layer
// or, if it is not being created based on an image, the Store object.
types.IDMappingOptions
LabelOpts []string
// Flags is a set of named flags and their values to store with the container.
// Currently these can only be set when the container record is created, but that
// could change in the future.
Flags map[string]any
MountOpts []string
Volatile bool
StorageOpt map[string]string
// Metadata is caller-specified metadata associated with the container.
Metadata string
// BigData is a set of items which should be stored for the container.
BigData []ContainerBigDataOption
}
type ContainerBigDataOption struct {
Key string
Data []byte
}
type store struct {
// # Locking hierarchy:
// These locks do not all need to be held simultaneously, but if some code does need to lock more than one, it MUST do so in this order:
// - graphLock
// - layerStore.start{Reading,Writing}
// - roLayerStores[].startReading (in the order of the items of the roLayerStores array)
// - imageStore.start{Reading,Writing}
// - roImageStores[].startReading (in the order of the items of the roImageStores array)
// - containerStore.start{Reading,Writing}
// The following fields are only set when constructing store, and must never be modified afterwards.
// They are safe to access without any other locking.
runRoot string
graphDriverName string // Initially set to the user-requested value, possibly ""; updated during store construction, and does not change afterwards.
graphDriverPriority []string
// graphLock:
// - Ensures that we always reload graphDriver, and the primary layer store, after any process does store.Shutdown. This is necessary
// because (??) the Shutdown may forcibly unmount and clean up, affecting graph driver state in a way only a graph driver
// and layer store reinitialization can notice.
// - Ensures that store.Shutdown is exclusive with mount operations. This is necessary at because some
// graph drivers call mount.MakePrivate() during initialization, the mount operations require that, and the drivers Cleanup() method
// may undo that. So, holding graphLock is required throughout the duration of Shutdown(), and the duration of any mount
// (but not unmount) calls.
// - Within this store object, protects access to some related in-memory state.
graphLock *lockfile.LockFile
usernsLock *lockfile.LockFile
graphRoot string
graphOptions []string
imageStoreDir string
pullOptions map[string]string
uidMap []idtools.IDMap
gidMap []idtools.IDMap
autoUsernsUser string
autoNsMinSize uint32
autoNsMaxSize uint32
imageStore rwImageStore
rwImageStores []rwImageStore
roImageStores []roImageStore
containerStore rwContainerStore
digestLockRoot string
disableVolatile bool
transientStore bool
// The following fields can only be accessed with graphLock held.
graphLockLastWrite lockfile.LastWrite
// FIXME: This field is only set when holding graphLock, but locking rules of the driver
// interface itself are not documented here. It is extensively used without holding graphLock.
graphDriver drivers.Driver
layerStoreUseGetters rwLayerStore // Almost all users should use the provided accessors instead of accessing this field directly.
roLayerStoresUseGetters []roLayerStore // Almost all users should use the provided accessors instead of accessing this field directly.
// FIXME: The following fields need locking, and dont have it.
additionalUIDs *idSet // Set by getAvailableIDs()
additionalGIDs *idSet // Set by getAvailableIDs()
}
// GetStore attempts to find an already-created Store object matching the
// specified location and graph driver, and if it can't, it creates and
// initializes a new Store object, and the underlying storage that it controls.
//
// If StoreOptions `options` haven't been fully populated, then DefaultStoreOptions are used.
//
// These defaults observe environment variables:
// - `STORAGE_DRIVER` for the name of the storage driver to attempt to use
// - `STORAGE_OPTS` for the string of options to pass to the driver
//
// Note that we do some of this work in a child process. The calling process's
// main() function needs to import our pkg/reexec package and should begin with
// something like this in order to allow us to properly start that child
// process:
//
// if reexec.Init() {
// return
// }
func GetStore(options types.StoreOptions) (Store, error) {
defaultOpts, err := types.Options()
if err != nil {
return nil, err
}
if options.RunRoot == "" && options.GraphRoot == "" && options.GraphDriverName == "" && len(options.GraphDriverOptions) == 0 {
options = defaultOpts
}
if options.GraphRoot != "" {
dir, err := filepath.Abs(options.GraphRoot)
if err != nil {
return nil, err
}
options.GraphRoot = dir
}
if options.RunRoot != "" {
dir, err := filepath.Abs(options.RunRoot)
if err != nil {
return nil, err
}
options.RunRoot = dir
}
storesLock.Lock()
defer storesLock.Unlock()
// return if BOTH run and graph root are matched, otherwise our run-root can be overridden if the graph is found first
for _, s := range stores {
if (s.graphRoot == options.GraphRoot) && (s.runRoot == options.RunRoot) && (options.GraphDriverName == "" || s.graphDriverName == options.GraphDriverName) {
return s, nil
}
}
// if passed a run-root or graph-root alone, the other should be defaulted only error if we have neither.
switch {
case options.RunRoot == "" && options.GraphRoot == "":
return nil, fmt.Errorf("no storage runroot or graphroot specified: %w", ErrIncompleteOptions)
case options.GraphRoot == "":
options.GraphRoot = defaultOpts.GraphRoot
case options.RunRoot == "":
options.RunRoot = defaultOpts.RunRoot
}
if err := os.MkdirAll(options.RunRoot, 0o700); err != nil {
return nil, err
}
if err := os.MkdirAll(options.GraphRoot, 0o700); err != nil {
return nil, err
}
if options.ImageStore != "" {
if err := os.MkdirAll(options.ImageStore, 0o700); err != nil {
return nil, err
}
}
if err := os.MkdirAll(filepath.Join(options.GraphRoot, options.GraphDriverName), 0o700); err != nil {
return nil, err
}
if options.ImageStore != "" {
if err := os.MkdirAll(filepath.Join(options.ImageStore, options.GraphDriverName), 0o700); err != nil {
return nil, err
}
}
graphLock, err := lockfile.GetLockFile(filepath.Join(options.GraphRoot, "storage.lock"))
if err != nil {
return nil, err
}
usernsLock, err := lockfile.GetLockFile(filepath.Join(options.GraphRoot, "userns.lock"))
if err != nil {
return nil, err
}
autoNsMinSize := options.AutoNsMinSize
autoNsMaxSize := options.AutoNsMaxSize
if autoNsMinSize == 0 {
autoNsMinSize = AutoUserNsMinSize
}
if autoNsMaxSize == 0 {
autoNsMaxSize = AutoUserNsMaxSize
}
s := &store{
runRoot: options.RunRoot,
graphDriverName: options.GraphDriverName,
graphDriverPriority: options.GraphDriverPriority,
graphLock: graphLock,
usernsLock: usernsLock,
graphRoot: options.GraphRoot,
graphOptions: options.GraphDriverOptions,
imageStoreDir: options.ImageStore,
pullOptions: options.PullOptions,
uidMap: copySlicePreferringNil(options.UIDMap),
gidMap: copySlicePreferringNil(options.GIDMap),
autoUsernsUser: options.RootAutoNsUser,
autoNsMinSize: autoNsMinSize,
autoNsMaxSize: autoNsMaxSize,
disableVolatile: options.DisableVolatile,
transientStore: options.TransientStore,
additionalUIDs: nil,
additionalGIDs: nil,
}
if err := s.load(); err != nil {
return nil, err
}
stores = append(stores, s)
return s, nil
}
func (s *store) RunRoot() string {
return s.runRoot
}
func (s *store) GraphDriverName() string {
return s.graphDriverName
}
func (s *store) GraphRoot() string {
return s.graphRoot
}
func (s *store) ImageStore() string {
return s.imageStoreDir
}
func (s *store) TransientStore() bool {
return s.transientStore
}
func (s *store) GraphOptions() []string {
return s.graphOptions
}
func (s *store) PullOptions() map[string]string {
cp := make(map[string]string, len(s.pullOptions))
maps.Copy(cp, s.pullOptions)
return cp
}
func (s *store) UIDMap() []idtools.IDMap {
return copySlicePreferringNil(s.uidMap)
}
func (s *store) GIDMap() []idtools.IDMap {
return copySlicePreferringNil(s.gidMap)
}
// This must only be called when constructing store; it writes to fields that are assumed to be constant after construction.
func (s *store) load() error {
var driver drivers.Driver
if err := func() error { // A scope for defer
s.graphLock.Lock()
defer s.graphLock.Unlock()
lastWrite, err := s.graphLock.GetLastWrite()
if err != nil {
return err
}
s.graphLockLastWrite = lastWrite
driver, err = s.createGraphDriverLocked()
if err != nil {
return err
}
s.graphDriver = driver
s.graphDriverName = driver.String()
return nil
}(); err != nil {
return err
}
driverPrefix := s.graphDriverName + "-"
imgStoreRoot := s.imageStoreDir
if imgStoreRoot == "" {
imgStoreRoot = s.graphRoot
}
gipath := filepath.Join(imgStoreRoot, driverPrefix+"images")
if err := os.MkdirAll(gipath, 0o700); err != nil {
return err
}
imageStore, err := newImageStore(gipath)
if err != nil {
return err
}
s.imageStore = imageStore
s.rwImageStores = []rwImageStore{imageStore}
gcpath := filepath.Join(s.graphRoot, driverPrefix+"containers")
if err := os.MkdirAll(gcpath, 0o700); err != nil {
return err
}
rcpath := filepath.Join(s.runRoot, driverPrefix+"containers")
if err := os.MkdirAll(rcpath, 0o700); err != nil {
return err
}
rcs, err := newContainerStore(gcpath, rcpath, s.transientStore)
if err != nil {
return err
}
s.containerStore = rcs
additionalImageStores := s.graphDriver.AdditionalImageStores()
if s.imageStoreDir != "" {
additionalImageStores = append([]string{s.graphRoot}, additionalImageStores...)
}
for _, store := range additionalImageStores {
gipath := filepath.Join(store, driverPrefix+"images")
var ris roImageStore
// both the graphdriver and the imagestore must be used read-write.
if store == s.imageStoreDir || store == s.graphRoot {
imageStore, err := newImageStore(gipath)
if err != nil {
return err
}
s.rwImageStores = append(s.rwImageStores, imageStore)
ris = imageStore
} else {
ris, err = newROImageStore(gipath)
if err != nil {
if errors.Is(err, syscall.EROFS) {
logrus.Debugf("Ignoring creation of lockfiles on read-only file systems %q, %v", gipath, err)
continue
}
return err
}
}
s.roImageStores = append(s.roImageStores, ris)
}
s.digestLockRoot = filepath.Join(s.runRoot, driverPrefix+"locks")
if err := os.MkdirAll(s.digestLockRoot, 0o700); err != nil {
return err
}
return nil
}
// GetDigestLock returns a digest-specific Locker.
func (s *store) GetDigestLock(d digest.Digest) (Locker, error) {
return lockfile.GetLockFile(filepath.Join(s.digestLockRoot, d.String()))
}
// startUsingGraphDriver obtains s.graphLock and ensures that s.graphDriver is set and fresh.
// It only intended to be used on a fully-constructed store.
// If this succeeds, the caller MUST call stopUsingGraphDriver().
func (s *store) startUsingGraphDriver() error {
s.graphLock.Lock()
succeeded := false
defer func() {
if !succeeded {
s.graphLock.Unlock()
}
}()
lastWrite, modified, err := s.graphLock.ModifiedSince(s.graphLockLastWrite)
if err != nil {
return err
}
if modified {
driver, err := s.createGraphDriverLocked()
if err != nil {
return err
}
// Our concurrency design requires s.graphDriverName not to be modified after
// store is constructed.
// Its fine for driver.String() not to match the requested graph driver name
// (e.g. if the user asks for overlay2 and gets overlay), but it must be an idempotent
// mapping:
// driver1 := drivers.New(userInput, config)
// name1 := driver1.String()
// name2 := drivers.New(name1, config).String()
// assert(name1 == name2)
if s.graphDriverName != driver.String() {
return fmt.Errorf("graph driver name changed from %q to %q during reload",
s.graphDriverName, driver.String())
}
s.graphDriver = driver
s.layerStoreUseGetters = nil
s.graphLockLastWrite = lastWrite
}
succeeded = true
return nil
}
// stopUsingGraphDriver releases graphLock obtained by startUsingGraphDriver.
func (s *store) stopUsingGraphDriver() {
s.graphLock.Unlock()
}
// createGraphDriverLocked creates a new instance of graph driver for s, and returns it.
// Almost all users should use startUsingGraphDriver instead.
// The caller must hold s.graphLock.
func (s *store) createGraphDriverLocked() (drivers.Driver, error) {
config := drivers.Options{
Root: s.graphRoot,
ImageStore: s.imageStoreDir,
RunRoot: s.runRoot,
DriverPriority: s.graphDriverPriority,
DriverOptions: s.graphOptions,
}
return drivers.New(s.graphDriverName, config)
}
func (s *store) GraphDriver() (drivers.Driver, error) {
if err := s.startUsingGraphDriver(); err != nil {
return nil, err
}
defer s.stopUsingGraphDriver()
return s.graphDriver, nil
}
// getLayerStoreLocked obtains and returns a handle to the writeable layer store object
// used by the Store.
// It must be called with s.graphLock held.
func (s *store) getLayerStoreLocked() (rwLayerStore, error) {
if s.layerStoreUseGetters != nil {
return s.layerStoreUseGetters, nil
}
driverPrefix := s.graphDriverName + "-"
rlpath := filepath.Join(s.runRoot, driverPrefix+"layers")
if err := os.MkdirAll(rlpath, 0o700); err != nil {
return nil, err
}
glpath := filepath.Join(s.graphRoot, driverPrefix+"layers")
if err := os.MkdirAll(glpath, 0o700); err != nil {
return nil, err
}
ilpath := ""
if s.imageStoreDir != "" {
ilpath = filepath.Join(s.imageStoreDir, driverPrefix+"layers")
}
rls, err := s.newLayerStore(rlpath, glpath, ilpath, s.graphDriver, s.transientStore)
if err != nil {
return nil, err
}
s.layerStoreUseGetters = rls
return s.layerStoreUseGetters, nil
}
// getLayerStore obtains and returns a handle to the writeable layer store object
// used by the store.
// It must be called WITHOUT s.graphLock held.
func (s *store) getLayerStore() (rwLayerStore, error) {
if err := s.startUsingGraphDriver(); err != nil {
return nil, err
}
defer s.stopUsingGraphDriver()
return s.getLayerStoreLocked()
}
// getROLayerStoresLocked obtains additional read/only layer store objects used by the
// Store.
// It must be called with s.graphLock held.
func (s *store) getROLayerStoresLocked() ([]roLayerStore, error) {
if s.roLayerStoresUseGetters != nil {
return s.roLayerStoresUseGetters, nil
}
driverPrefix := s.graphDriverName + "-"
rlpath := filepath.Join(s.runRoot, driverPrefix+"layers")
if err := os.MkdirAll(rlpath, 0o700); err != nil {
return nil, err
}
for _, store := range s.graphDriver.AdditionalImageStores() {
glpath := filepath.Join(store, driverPrefix+"layers")
rls, err := newROLayerStore(rlpath, glpath, s.graphDriver)
if err != nil {
return nil, err
}
s.roLayerStoresUseGetters = append(s.roLayerStoresUseGetters, rls)
}
return s.roLayerStoresUseGetters, nil
}
// bothLayerStoreKindsLocked returns the primary, and additional read-only, layer store objects used by the store.
// It must be called with s.graphLock held.
func (s *store) bothLayerStoreKindsLocked() (rwLayerStore, []roLayerStore, error) {
primary, err := s.getLayerStoreLocked()
if err != nil {
return nil, nil, fmt.Errorf("loading primary layer store data: %w", err)
}
additional, err := s.getROLayerStoresLocked()
if err != nil {
return nil, nil, fmt.Errorf("loading additional layer stores: %w", err)
}
return primary, additional, nil
}
// bothLayerStoreKinds returns the primary, and additional read-only, layer store objects used by the store.
// It must be called WITHOUT s.graphLock held.
func (s *store) bothLayerStoreKinds() (rwLayerStore, []roLayerStore, error) {
if err := s.startUsingGraphDriver(); err != nil {
return nil, nil, err
}
defer s.stopUsingGraphDriver()
return s.bothLayerStoreKindsLocked()
}
// allLayerStores returns a list of all layer store objects used by the Store.
// This is a convenience method for read-only users of the Store.
// It must be called with s.graphLock held.
func (s *store) allLayerStoresLocked() ([]roLayerStore, error) {
primary, additional, err := s.bothLayerStoreKindsLocked()
if err != nil {
return nil, err
}
return append([]roLayerStore{primary}, additional...), nil
}
// allLayerStores returns a list of all layer store objects used by the Store.
// This is a convenience method for read-only users of the Store.
// It must be called WITHOUT s.graphLock held.
func (s *store) allLayerStores() ([]roLayerStore, error) {
if err := s.startUsingGraphDriver(); err != nil {
return nil, err
}
defer s.stopUsingGraphDriver()
return s.allLayerStoresLocked()
}
// readAllLayerStores processes allLayerStores() in order:
// It locks the store for reading, checks for updates, and calls
//
// (data, done, err) := fn(store)
//
// until the callback returns done == true, and returns the data from the callback.
//
// If reading any layer store fails, it immediately returns ({}, true, err).
//
// If all layer stores are processed without setting done == true, it returns ({}, false, nil).
//
// Typical usage:
//
// if res, done, err := s.readAllLayerStores(store, func(…) {
// …
// }; done {
// return res, err
// }
func readAllLayerStores[T any](s *store, fn func(store roLayerStore) (T, bool, error)) (T, bool, error) {
var zeroRes T // A zero value of T
layerStores, err := s.allLayerStores()
if err != nil {
return zeroRes, true, err
}
for _, s := range layerStores {
store := s
if err := store.startReading(); err != nil {
return zeroRes, true, err
}
defer store.stopReading()
if res, done, err := fn(store); done {
return res, true, err
}
}
return zeroRes, false, nil
}
// writeToLayerStore is a helper for working with store.getLayerStore():
// It locks the store for writing, checks for updates, and calls fn()
// It returns the return value of fn, or its own error initializing the store.
func writeToLayerStore[T any](s *store, fn func(store rwLayerStore) (T, error)) (T, error) {
var zeroRes T // A zero value of T
store, err := s.getLayerStore()
if err != nil {
return zeroRes, err
}
if err := store.startWriting(); err != nil {
return zeroRes, err
}
defer store.stopWriting()
return fn(store)
}
// readOrWriteAllLayerStores processes allLayerStores() in order:
// It locks the writeable store for writing and all others for reading, checks
// for updates, and calls
//
// (data, done, err) := fn(store)
//
// until the callback returns done == true, and returns the data from the callback.
//
// If reading or writing any layer store fails, it immediately returns ({}, true, err).
//
// If all layer stores are processed without setting done == true, it returns ({}, false, nil).
//
// Typical usage:
//
// if res, done, err := s.readOrWriteAllLayerStores(store, func(…) {
// …
// }; done {
// return res, err
// }
func readOrWriteAllLayerStores[T any](s *store, fn func(store roLayerStore) (T, bool, error)) (T, bool, error) {
var zeroRes T // A zero value of T
rwLayerStore, roLayerStores, err := s.bothLayerStoreKinds()
if err != nil {
return zeroRes, true, err
}
if err := rwLayerStore.startWriting(); err != nil {
return zeroRes, true, err
}
defer rwLayerStore.stopWriting()
if res, done, err := fn(rwLayerStore); done {
return res, true, err
}
for _, s := range roLayerStores {
store := s
if err := store.startReading(); err != nil {
return zeroRes, true, err
}
defer store.stopReading()
if res, done, err := fn(store); done {
return res, true, err
}
}
return zeroRes, false, nil
}
// allImageStores returns a list of all image store objects used by the Store.
// This is a convenience method for read-only users of the Store.
func (s *store) allImageStores() []roImageStore {
return append([]roImageStore{s.imageStore}, s.roImageStores...)
}
// readAllImageStores processes allImageStores() in order:
// It locks the store for reading, checks for updates, and calls
//
// (data, done, err) := fn(store)
//
// until the callback returns done == true, and returns the data from the callback.
//
// If reading any Image store fails, it immediately returns ({}, true, err).
//
// If all Image stores are processed without setting done == true, it returns ({}, false, nil).
//
// Typical usage:
//
// if res, done, err := readAllImageStores(store, func(…) {
// …
// }; done {
// return res, err
// }
func readAllImageStores[T any](s *store, fn func(store roImageStore) (T, bool, error)) (T, bool, error) {
var zeroRes T // A zero value of T
for _, s := range s.allImageStores() {
store := s
if err := store.startReading(); err != nil {
return zeroRes, true, err
}
defer store.stopReading()
if res, done, err := fn(store); done {
return res, true, err
}
}
return zeroRes, false, nil
}
// writeToImageStore is a convenience helper for working with store.imageStore:
// It locks the store for writing, checks for updates, and calls fn(), which can then access store.imageStore.
// It returns the return value of fn, or its own error initializing the store.
func writeToImageStore[T any](s *store, fn func() (T, error)) (T, error) {
if err := s.imageStore.startWriting(); err != nil {
var zeroRes T // A zero value of T
return zeroRes, err
}
defer s.imageStore.stopWriting()
return fn()
}
// readContainerStore is a convenience helper for working with store.containerStore:
// It locks the store for reading, checks for updates, and calls fn(), which can then access store.containerStore.
// If reading the container store fails, it returns ({}, true, err).
// Returns the return value of fn on success.
func readContainerStore[T any](s *store, fn func() (T, bool, error)) (T, bool, error) {
if err := s.containerStore.startReading(); err != nil {
var zeroRes T // A zero value of T
return zeroRes, true, err
}
defer s.containerStore.stopReading()
return fn()
}
// writeToContainerStore is a convenience helper for working with store.containerStore:
// It locks the store for writing, checks for updates, and calls fn(), which can then access store.containerStore.
// It returns the return value of fn, or its own error initializing the store.
func writeToContainerStore[T any](s *store, fn func() (T, error)) (T, error) {
if err := s.containerStore.startWriting(); err != nil {
var zeroRes T // A zero value of T
return zeroRes, err
}
defer s.containerStore.stopWriting()
return fn()
}
// writeToAllStores is a convenience helper for writing to all three stores:
// It locks the stores for writing, checks for updates, and calls fn(), which can then access the provided layer store,
// s.imageStore and s.containerStore.
// It returns the return value of fn, or its own error initializing the stores.
func (s *store) writeToAllStores(fn func(rlstore rwLayerStore) error) error {
rlstore, err := s.getLayerStore()
if err != nil {
return err
}
if err := rlstore.startWriting(); err != nil {
return err
}
defer rlstore.stopWriting()
if err := s.imageStore.startWriting(); err != nil {
return err
}
defer s.imageStore.stopWriting()
if err := s.containerStore.startWriting(); err != nil {
return err
}
defer s.containerStore.stopWriting()
return fn(rlstore)
}
// canUseShifting returns true if we can use mount-time arguments (shifting) to
// avoid having to create a mapped top layer for a base image when we want to
// use it to create a container using ID mappings.
// On entry:
// - rlstore must be locked for writing
func (s *store) canUseShifting(uidmap, gidmap []idtools.IDMap) bool {
if !s.graphDriver.SupportsShifting() {
return false
}
if uidmap != nil && !idtools.IsContiguous(uidmap) {
return false
}
if gidmap != nil && !idtools.IsContiguous(gidmap) {
return false
}
return true
}
// On entry:
// - rlstore must be locked for writing
// - rlstores MUST NOT be locked
func (s *store) putLayer(rlstore rwLayerStore, rlstores []roLayerStore, id, parent string, names []string, mountLabel string, writeable bool, lOptions *LayerOptions, diff io.Reader, slo *stagedLayerOptions) (*Layer, int64, error) {
var parentLayer *Layer
var options LayerOptions
if lOptions != nil {
options = *lOptions
options.BigData = slices.Clone(lOptions.BigData)
options.Flags = copyMapPreferringNil(lOptions.Flags)
}
if options.HostUIDMapping {
options.UIDMap = nil
}
if options.HostGIDMapping {
options.GIDMap = nil
}
uidMap := options.UIDMap
gidMap := options.GIDMap
if parent != "" {
var ilayer *Layer
for _, l := range append([]roLayerStore{rlstore}, rlstores...) {
lstore := l
if lstore != rlstore {
if err := lstore.startReading(); err != nil {
return nil, -1, err
}
defer lstore.stopReading()
}
if l, err := lstore.Get(parent); err == nil && l != nil {
ilayer = l
parent = ilayer.ID
break
}
}
if ilayer == nil {
return nil, -1, ErrLayerUnknown
}
parentLayer = ilayer
if err := s.containerStore.startWriting(); err != nil {
return nil, -1, err
}
defer s.containerStore.stopWriting()
containers, err := s.containerStore.Containers()
if err != nil {
return nil, -1, err
}
for _, container := range containers {
if container.LayerID == parent {
return nil, -1, ErrParentIsContainer
}
}
if !options.HostUIDMapping && len(options.UIDMap) == 0 {
uidMap = ilayer.UIDMap
}
if !options.HostGIDMapping && len(options.GIDMap) == 0 {
gidMap = ilayer.GIDMap
}
} else {
// FIXME? Its unclear why we are holding containerStore locked here at all
// (and because we are not modifying it, why it is a write lock, not a read lock).
if err := s.containerStore.startWriting(); err != nil {
return nil, -1, err
}
defer s.containerStore.stopWriting()
if !options.HostUIDMapping && len(options.UIDMap) == 0 {
uidMap = s.uidMap
}
if !options.HostGIDMapping && len(options.GIDMap) == 0 {
gidMap = s.gidMap
}
}
if s.canUseShifting(uidMap, gidMap) {
options.IDMappingOptions = types.IDMappingOptions{HostUIDMapping: true, HostGIDMapping: true, UIDMap: nil, GIDMap: nil}
} else {
options.IDMappingOptions = types.IDMappingOptions{
HostUIDMapping: options.HostUIDMapping,
HostGIDMapping: options.HostGIDMapping,
UIDMap: copySlicePreferringNil(uidMap),
GIDMap: copySlicePreferringNil(gidMap),
}
}
return rlstore.create(id, parentLayer, names, mountLabel, nil, &options, writeable, diff, slo)
}
func (s *store) PutLayer(id, parent string, names []string, mountLabel string, writeable bool, lOptions *LayerOptions, diff io.Reader) (*Layer, int64, error) {
rlstore, rlstores, err := s.bothLayerStoreKinds()
if err != nil {
return nil, -1, err
}
if err := rlstore.startWriting(); err != nil {
return nil, -1, err
}
defer rlstore.stopWriting()
return s.putLayer(rlstore, rlstores, id, parent, names, mountLabel, writeable, lOptions, diff, nil)
}
func (s *store) CreateLayer(id, parent string, names []string, mountLabel string, writeable bool, options *LayerOptions) (*Layer, error) {
layer, _, err := s.PutLayer(id, parent, names, mountLabel, writeable, options, nil)
return layer, err
}
func (s *store) CreateImage(id string, names []string, layer, metadata string, iOptions *ImageOptions) (*Image, error) {
if layer != "" {
layerStores, err := s.allLayerStores()
if err != nil {
return nil, err
}
var ilayer *Layer
for _, s := range layerStores {
store := s
if err := store.startReading(); err != nil {
return nil, err
}
defer store.stopReading()
ilayer, err = store.Get(layer)
if err == nil {
break
}
}
if ilayer == nil {
return nil, ErrLayerUnknown
}
layer = ilayer.ID
}
return writeToImageStore(s, func() (*Image, error) {
var options ImageOptions
var namesToAddAfterCreating []string
// Check if the ID refers to an image in a read-only store -- we want
// to allow images in read-only stores to have their names changed, so
// if we find one, merge the new values in with what we know about the
// image that's already there.
if id != "" {
for _, is := range s.roImageStores {
store := is
if err := store.startReading(); err != nil {
return nil, err
}
defer store.stopReading()
if i, err := store.Get(id); err == nil {
// set information about this image in "options"
options = ImageOptions{
Metadata: i.Metadata,
CreationDate: i.Created,
Digest: i.Digest,
Digests: copySlicePreferringNil(i.Digests),
NamesHistory: copySlicePreferringNil(i.NamesHistory),
}
for _, key := range i.BigDataNames {
data, err := store.BigData(id, key)
if err != nil {
return nil, err
}
dataDigest, err := store.BigDataDigest(id, key)
if err != nil {
return nil, err
}
options.BigData = append(options.BigData, ImageBigDataOption{
Key: key,
Data: data,
Digest: dataDigest,
})
}
namesToAddAfterCreating = dedupeStrings(slices.Concat(i.Names, names))
break
}
}
}
// merge any passed-in options into "options" as best we can
if iOptions != nil {
if !iOptions.CreationDate.IsZero() {
options.CreationDate = iOptions.CreationDate
}
if iOptions.Digest != "" {
options.Digest = iOptions.Digest
}
options.Digests = append(options.Digests, iOptions.Digests...)
if iOptions.Metadata != "" {
options.Metadata = iOptions.Metadata
}
options.BigData = append(options.BigData, copyImageBigDataOptionSlice(iOptions.BigData)...)
options.NamesHistory = append(options.NamesHistory, iOptions.NamesHistory...)
if options.Flags == nil {
options.Flags = make(map[string]any)
}
maps.Copy(options.Flags, iOptions.Flags)
}
if options.CreationDate.IsZero() {
options.CreationDate = time.Now().UTC()
}
if metadata != "" {
options.Metadata = metadata
}
res, err := s.imageStore.create(id, names, layer, options)
if err == nil && len(namesToAddAfterCreating) > 0 {
// set any names we pulled up from an additional image store, now that we won't be
// triggering a duplicate names error
err = s.imageStore.updateNames(res.ID, namesToAddAfterCreating, addNames)
}
return res, err
})
}
// imageTopLayerForMapping locates the layer that can take the place of the
// image's top layer as the shared parent layer for a one or more containers
// which are using ID mappings.
// On entry:
// - ristore must be locked EITHER for reading or writing
// - s.imageStore must be locked for writing; it might be identical to ristore.
// - rlstore must be locked for writing
// - lstores must all be locked for reading
func (s *store) imageTopLayerForMapping(image *Image, ristore roImageStore, rlstore rwLayerStore, lstores []roLayerStore, options types.IDMappingOptions) (*Layer, error) {
layerMatchesMappingOptions := func(layer *Layer, options types.IDMappingOptions) bool {
// If the driver supports shifting and the layer has no mappings, we can use it.
if s.canUseShifting(options.UIDMap, options.GIDMap) && len(layer.UIDMap) == 0 && len(layer.GIDMap) == 0 {
return true
}
// If we want host mapping, and the layer uses mappings, it's not the best match.
if options.HostUIDMapping && len(layer.UIDMap) != 0 {
return false
}
if options.HostGIDMapping && len(layer.GIDMap) != 0 {
return false
}
// Compare the maps.
return reflect.DeepEqual(layer.UIDMap, options.UIDMap) && reflect.DeepEqual(layer.GIDMap, options.GIDMap)
}
var layer, parentLayer *Layer
allStores := append([]roLayerStore{rlstore}, lstores...)
// Locate the image's top layer and its parent, if it has one.
createMappedLayer := ristore == s.imageStore
for _, s := range allStores {
store := s
// Walk the top layer list.
for _, candidate := range append([]string{image.TopLayer}, image.MappedTopLayers...) {
if cLayer, err := store.Get(candidate); err == nil {
// We want the layer's parent, too, if it has one.
var cParentLayer *Layer
if cLayer.Parent != "" {
// Its parent should be in one of the stores, somewhere.
for _, ps := range allStores {
if cParentLayer, err = ps.Get(cLayer.Parent); err == nil {
break
}
}
if cParentLayer == nil {
continue
}
}
// If the layer matches the desired mappings, it's a perfect match,
// so we're actually done here.
if layerMatchesMappingOptions(cLayer, options) {
return cLayer, nil
}
// Record the first one that we found, even if it's not ideal, so that
// we have a starting point.
if layer == nil {
layer = cLayer
parentLayer = cParentLayer
if store != rlstore {
// The layer is in another store, so we cannot
// create a mapped version of it to the image.
createMappedLayer = false
}
}
}
}
}
if layer == nil {
return nil, ErrLayerUnknown
}
// The top layer's mappings don't match the ones we want, but it's in a read-only
// image store, so we can't create and add a mapped copy of the layer to the image.
// We'll have to do the mapping for the container itself, elsewhere.
if !createMappedLayer {
return layer, nil
}
// The top layer's mappings don't match the ones we want, and it's in an image store
// that lets us edit image metadata, so create a duplicate of the layer with the desired
// mappings, and register it as an alternate top layer in the image.
var layerOptions LayerOptions
if s.canUseShifting(options.UIDMap, options.GIDMap) {
layerOptions.IDMappingOptions = types.IDMappingOptions{
HostUIDMapping: true,
HostGIDMapping: true,
UIDMap: nil,
GIDMap: nil,
}
} else {
layerOptions.IDMappingOptions = types.IDMappingOptions{
HostUIDMapping: options.HostUIDMapping,
HostGIDMapping: options.HostGIDMapping,
UIDMap: copySlicePreferringNil(options.UIDMap),
GIDMap: copySlicePreferringNil(options.GIDMap),
}
}
layerOptions.TemplateLayer = layer.ID
mappedLayer, _, err := rlstore.create("", parentLayer, nil, layer.MountLabel, nil, &layerOptions, false, nil, nil)
if err != nil {
return nil, fmt.Errorf("creating an ID-mapped copy of layer %q: %w", layer.ID, err)
}
// By construction, createMappedLayer can only be true if ristore == s.imageStore.
if err = s.imageStore.addMappedTopLayer(image.ID, mappedLayer.ID); err != nil {
if err2 := rlstore.Delete(mappedLayer.ID); err2 != nil {
err = fmt.Errorf("deleting layer %q: %v: %w", mappedLayer.ID, err2, err)
}
return nil, fmt.Errorf("registering ID-mapped layer with image %q: %w", image.ID, err)
}
return mappedLayer, nil
}
func (s *store) CreateContainer(id string, names []string, image, layer, metadata string, cOptions *ContainerOptions) (*Container, error) {
var options ContainerOptions
if cOptions != nil {
options = *cOptions
options.IDMappingOptions.UIDMap = copySlicePreferringNil(cOptions.IDMappingOptions.UIDMap)
options.IDMappingOptions.GIDMap = copySlicePreferringNil(cOptions.IDMappingOptions.GIDMap)
options.LabelOpts = copySlicePreferringNil(cOptions.LabelOpts)
options.Flags = copyMapPreferringNil(cOptions.Flags)
options.MountOpts = copySlicePreferringNil(cOptions.MountOpts)
options.StorageOpt = copyMapPreferringNil(cOptions.StorageOpt)
options.BigData = copyContainerBigDataOptionSlice(cOptions.BigData)
}
if options.HostUIDMapping {
options.UIDMap = nil
}
if options.HostGIDMapping {
options.GIDMap = nil
}
options.Metadata = metadata
rlstore, lstores, err := s.bothLayerStoreKinds() // lstores will be locked read-only if image != ""
if err != nil {
return nil, err
}
var imageTopLayer *Layer
imageID := ""
if options.AutoUserNs || options.UIDMap != nil || options.GIDMap != nil {
// Prevent multiple instances to retrieve the same range when AutoUserNs
// are used.
// It doesn't prevent containers that specify an explicit mapping to overlap
// with AutoUserNs.
s.usernsLock.Lock()
defer s.usernsLock.Unlock()
}
var imageHomeStore roImageStore // Set if image != ""
// s.imageStore is locked read-write, if image != ""
// s.roImageStores are NOT NECESSARILY ALL locked read-only if image != ""
var cimage *Image // Set if image != ""
if image != "" {
if err := rlstore.startWriting(); err != nil {
return nil, err
}
defer rlstore.stopWriting()
for _, s := range lstores {
store := s
if err := store.startReading(); err != nil {
return nil, err
}
defer store.stopReading()
}
if err := s.imageStore.startWriting(); err != nil {
return nil, err
}
defer s.imageStore.stopWriting()
cimage, err = s.imageStore.Get(image)
if err == nil {
imageHomeStore = s.imageStore
} else {
for _, s := range s.roImageStores {
store := s
if err := store.startReading(); err != nil {
return nil, err
}
defer store.stopReading()
cimage, err = store.Get(image)
if err == nil {
imageHomeStore = store
break
}
}
}
if cimage == nil {
return nil, fmt.Errorf("locating image with ID %q: %w", image, ErrImageUnknown)
}
imageID = cimage.ID
}
if options.AutoUserNs {
var err error
options.UIDMap, options.GIDMap, err = s.getAutoUserNS(&options.AutoUserNsOpts, cimage, rlstore, lstores)
if err != nil {
return nil, err
}
}
uidMap := options.UIDMap
gidMap := options.GIDMap
idMappingsOptions := options.IDMappingOptions
if image != "" {
if cimage.TopLayer != "" {
ilayer, err := s.imageTopLayerForMapping(cimage, imageHomeStore, rlstore, lstores, idMappingsOptions)
if err != nil {
return nil, err
}
imageTopLayer = ilayer
if !options.HostUIDMapping && len(options.UIDMap) == 0 {
uidMap = ilayer.UIDMap
}
if !options.HostGIDMapping && len(options.GIDMap) == 0 {
gidMap = ilayer.GIDMap
}
}
} else {
if err := rlstore.startWriting(); err != nil {
return nil, err
}
defer rlstore.stopWriting()
if !options.HostUIDMapping && len(options.UIDMap) == 0 {
uidMap = s.uidMap
}
if !options.HostGIDMapping && len(options.GIDMap) == 0 {
gidMap = s.gidMap
}
}
layerOptions := &LayerOptions{
// Normally layers for containers are volatile only if the container is.
// But in transient store mode, all container layers are volatile.
Volatile: options.Volatile || s.transientStore,
}
if s.canUseShifting(uidMap, gidMap) {
layerOptions.IDMappingOptions = types.IDMappingOptions{
HostUIDMapping: true,
HostGIDMapping: true,
UIDMap: nil,
GIDMap: nil,
}
} else {
layerOptions.IDMappingOptions = types.IDMappingOptions{
HostUIDMapping: idMappingsOptions.HostUIDMapping,
HostGIDMapping: idMappingsOptions.HostGIDMapping,
UIDMap: copySlicePreferringNil(uidMap),
GIDMap: copySlicePreferringNil(gidMap),
}
}
if options.Flags == nil {
options.Flags = make(map[string]any)
}
plabel, _ := options.Flags[processLabelFlag].(string)
mlabel, _ := options.Flags[mountLabelFlag].(string)
if (plabel == "" && mlabel != "") || (plabel != "" && mlabel == "") {
return nil, errors.New("ProcessLabel and Mountlabel must either not be specified or both specified")
}
if plabel == "" {
processLabel, mountLabel, err := label.InitLabels(options.LabelOpts)
if err != nil {
return nil, err
}
mlabel = mountLabel
options.Flags[processLabelFlag] = processLabel
options.Flags[mountLabelFlag] = mountLabel
}
clayer, _, err := rlstore.create(layer, imageTopLayer, nil, mlabel, options.StorageOpt, layerOptions, true, nil, nil)
if err != nil {
return nil, err
}
layer = clayer.ID
// Normally only `--rm` containers are volatile, but in transient store mode all containers are volatile
if s.transientStore {
options.Volatile = true
}
return writeToContainerStore(s, func() (*Container, error) {
options.IDMappingOptions = types.IDMappingOptions{
HostUIDMapping: len(options.UIDMap) == 0,
HostGIDMapping: len(options.GIDMap) == 0,
UIDMap: copySlicePreferringNil(options.UIDMap),
GIDMap: copySlicePreferringNil(options.GIDMap),
}
container, err := s.containerStore.create(id, names, imageID, layer, &options)
if err != nil || container == nil {
if err2 := rlstore.Delete(layer); err2 != nil {
if err == nil {
err = fmt.Errorf("deleting layer %#v: %w", layer, err2)
} else {
logrus.Errorf("While recovering from a failure to create a container, error deleting layer %#v: %v", layer, err2)
}
}
}
return container, err
})
}
func (s *store) SetMetadata(id, metadata string) error {
return s.writeToAllStores(func(rlstore rwLayerStore) error {
if rlstore.Exists(id) {
return rlstore.SetMetadata(id, metadata)
}
if s.imageStore.Exists(id) {
return s.imageStore.SetMetadata(id, metadata)
}
if s.containerStore.Exists(id) {
return s.containerStore.SetMetadata(id, metadata)
}
return ErrNotAnID
})
}
func (s *store) Metadata(id string) (string, error) {
if res, done, err := readAllLayerStores(s, func(store roLayerStore) (string, bool, error) {
if store.Exists(id) {
res, err := store.Metadata(id)
return res, true, err
}
return "", false, nil
}); done {
return res, err
}
if res, done, err := readAllImageStores(s, func(store roImageStore) (string, bool, error) {
if store.Exists(id) {
res, err := store.Metadata(id)
return res, true, err
}
return "", false, nil
}); done {
return res, err
}
if res, done, err := readContainerStore(s, func() (string, bool, error) {
if s.containerStore.Exists(id) {
res, err := s.containerStore.Metadata(id)
return res, true, err
}
return "", false, nil
}); done {
return res, err
}
return "", ErrNotAnID
}
func (s *store) ListImageBigData(id string) ([]string, error) {
if res, done, err := readAllImageStores(s, func(store roImageStore) ([]string, bool, error) {
bigDataNames, err := store.BigDataNames(id)
if err == nil {
return bigDataNames, true, nil
}
return nil, false, nil
}); done {
return res, err
}
return nil, fmt.Errorf("locating image with ID %q: %w", id, ErrImageUnknown)
}
func (s *store) ImageBigDataSize(id, key string) (int64, error) {
if res, done, err := readAllImageStores(s, func(store roImageStore) (int64, bool, error) {
size, err := store.BigDataSize(id, key)
if err == nil {
return size, true, nil
}
return -1, false, nil
}); done {
if err != nil {
return -1, err
}
return res, nil
}
return -1, ErrSizeUnknown
}
func (s *store) ImageBigDataDigest(id, key string) (digest.Digest, error) {
if res, done, err := readAllImageStores(s, func(ristore roImageStore) (digest.Digest, bool, error) {
d, err := ristore.BigDataDigest(id, key)
if err == nil && d.Validate() == nil {
return d, true, nil
}
return "", false, nil
}); done {
return res, err
}
return "", ErrDigestUnknown
}
func (s *store) ImageBigData(id, key string) ([]byte, error) {
foundImage := false
if res, done, err := readAllImageStores(s, func(store roImageStore) ([]byte, bool, error) {
data, err := store.BigData(id, key)
if err == nil {
return data, true, nil
}
if store.Exists(id) {
foundImage = true
}
return nil, false, nil
}); done {
return res, err
}
if foundImage {
return nil, fmt.Errorf("locating item named %q for image with ID %q (consider removing the image to resolve the issue): %w", key, id, os.ErrNotExist)
}
return nil, fmt.Errorf("locating image with ID %q: %w", id, ErrImageUnknown)
}
// ListLayerBigData retrieves a list of the (possibly large) chunks of
// named data associated with an layer.
func (s *store) ListLayerBigData(id string) ([]string, error) {
foundLayer := false
if res, done, err := readAllLayerStores(s, func(store roLayerStore) ([]string, bool, error) {
data, err := store.BigDataNames(id)
if err == nil {
return data, true, nil
}
if store.Exists(id) {
foundLayer = true
}
return nil, false, nil
}); done {
return res, err
}
if foundLayer {
return nil, fmt.Errorf("locating big data for layer with ID %q: %w", id, os.ErrNotExist)
}
return nil, fmt.Errorf("locating layer with ID %q: %w", id, ErrLayerUnknown)
}
// LayerBigData retrieves a (possibly large) chunk of named data
// associated with a layer.
func (s *store) LayerBigData(id, key string) (io.ReadCloser, error) {
foundLayer := false
if res, done, err := readAllLayerStores(s, func(store roLayerStore) (io.ReadCloser, bool, error) {
data, err := store.BigData(id, key)
if err == nil {
return data, true, nil
}
if store.Exists(id) {
foundLayer = true
}
return nil, false, nil
}); done {
return res, err
}
if foundLayer {
return nil, fmt.Errorf("locating item named %q for layer with ID %q: %w", key, id, os.ErrNotExist)
}
return nil, fmt.Errorf("locating layer with ID %q: %w", id, ErrLayerUnknown)
}
// SetLayerBigData stores a (possibly large) chunk of named data
// associated with a layer.
func (s *store) SetLayerBigData(id, key string, data io.Reader) error {
_, err := writeToLayerStore(s, func(store rwLayerStore) (struct{}, error) {
return struct{}{}, store.SetBigData(id, key, data)
})
return err
}
func (s *store) SetImageBigData(id, key string, data []byte, digestManifest func([]byte) (digest.Digest, error)) error {
_, err := writeToImageStore(s, func() (struct{}, error) {
return struct{}{}, s.imageStore.SetBigData(id, key, data, digestManifest)
})
return err
}
func (s *store) ImageSize(id string) (int64, error) {
layerStores, err := s.allLayerStores()
if err != nil {
return -1, err
}
for _, s := range layerStores {
store := s
if err := store.startReading(); err != nil {
return -1, err
}
defer store.stopReading()
}
// Look for the image's record.
var imageStore roBigDataStore
var image *Image
for _, s := range s.allImageStores() {
store := s
if err := store.startReading(); err != nil {
return -1, err
}
defer store.stopReading()
if image, err = store.Get(id); err == nil {
imageStore = store
break
}
}
if image == nil {
return -1, fmt.Errorf("locating image with ID %q: %w", id, ErrImageUnknown)
}
// Start with a list of the image's top layers, if it has any.
queue := make(map[string]struct{})
for _, layerID := range append([]string{image.TopLayer}, image.MappedTopLayers...) {
if layerID != "" {
queue[layerID] = struct{}{}
}
}
visited := make(map[string]struct{})
// Walk all of the layers.
var size int64
for len(visited) < len(queue) {
for layerID := range queue {
// Visit each layer only once.
if _, ok := visited[layerID]; ok {
continue
}
visited[layerID] = struct{}{}
// Look for the layer and the store that knows about it.
var layerStore roLayerStore
var layer *Layer
for _, store := range layerStores {
if layer, err = store.Get(layerID); err == nil {
layerStore = store
break
}
}
if layer == nil {
return -1, fmt.Errorf("locating layer with ID %q: %w", layerID, ErrLayerUnknown)
}
// The UncompressedSize is only valid if there's a digest to go with it.
n := layer.UncompressedSize
if layer.UncompressedDigest == "" || n == -1 {
// Compute the size.
n, err = layerStore.DiffSize("", layer.ID)
if err != nil {
return -1, fmt.Errorf("size/digest of layer with ID %q could not be calculated: %w", layerID, err)
}
}
// Count this layer.
size += n
// Make a note to visit the layer's parent if we haven't already.
if layer.Parent != "" {
queue[layer.Parent] = struct{}{}
}
}
}
// Count big data items.
names, err := imageStore.BigDataNames(id)
if err != nil {
return -1, fmt.Errorf("reading list of big data items for image %q: %w", id, err)
}
for _, name := range names {
n, err := imageStore.BigDataSize(id, name)
if err != nil {
return -1, fmt.Errorf("reading size of big data item %q for image %q: %w", name, id, err)
}
size += n
}
return size, nil
}
func (s *store) ContainerSize(id string) (int64, error) {
layerStores, err := s.allLayerStores()
if err != nil {
return -1, err
}
for _, s := range layerStores {
store := s
if err := store.startReading(); err != nil {
return -1, err
}
defer store.stopReading()
}
// Get the location of the container directory and container run directory.
// Do it before we lock the container store because they do, too.
cdir, err := s.ContainerDirectory(id)
if err != nil {
return -1, err
}
rdir, err := s.ContainerRunDirectory(id)
if err != nil {
return -1, err
}
return writeToContainerStore(s, func() (int64, error) { // Yes, s.containerStore.BigDataSize requires a write lock.
// Read the container record.
container, err := s.containerStore.Get(id)
if err != nil {
return -1, err
}
// Read the container's layer's size.
var layer *Layer
var size int64
for _, store := range layerStores {
if layer, err = store.Get(container.LayerID); err == nil {
size, err = store.DiffSize("", layer.ID)
if err != nil {
return -1, fmt.Errorf("determining size of layer with ID %q: %w", layer.ID, err)
}
break
}
}
if layer == nil {
return -1, fmt.Errorf("locating layer with ID %q: %w", container.LayerID, ErrLayerUnknown)
}
// Count big data items.
names, err := s.containerStore.BigDataNames(id)
if err != nil {
return -1, fmt.Errorf("reading list of big data items for container %q: %w", container.ID, err)
}
for _, name := range names {
n, err := s.containerStore.BigDataSize(id, name)
if err != nil {
return -1, fmt.Errorf("reading size of big data item %q for container %q: %w", name, id, err)
}
size += n
}
// Count the size of our container directory and container run directory.
n, err := directory.Size(cdir)
if err != nil {
return -1, err
}
size += n
n, err = directory.Size(rdir)
if err != nil {
return -1, err
}
size += n
return size, nil
})
}
func (s *store) ListContainerBigData(id string) ([]string, error) {
res, _, err := readContainerStore(s, func() ([]string, bool, error) {
res, err := s.containerStore.BigDataNames(id)
return res, true, err
})
return res, err
}
func (s *store) ContainerBigDataSize(id, key string) (int64, error) {
return writeToContainerStore(s, func() (int64, error) { // Yes, BigDataSize requires a write lock.
return s.containerStore.BigDataSize(id, key)
})
}
func (s *store) ContainerBigDataDigest(id, key string) (digest.Digest, error) {
return writeToContainerStore(s, func() (digest.Digest, error) { // Yes, BigDataDigest requires a write lock.
return s.containerStore.BigDataDigest(id, key)
})
}
func (s *store) ContainerBigData(id, key string) ([]byte, error) {
res, _, err := readContainerStore(s, func() ([]byte, bool, error) {
res, err := s.containerStore.BigData(id, key)
return res, true, err
})
return res, err
}
func (s *store) SetContainerBigData(id, key string, data []byte) error {
_, err := writeToContainerStore(s, func() (struct{}, error) {
return struct{}{}, s.containerStore.SetBigData(id, key, data)
})
return err
}
func (s *store) Exists(id string) bool {
found, _, err := readAllLayerStores(s, func(store roLayerStore) (bool, bool, error) {
if store.Exists(id) {
return true, true, nil
}
return false, false, nil
})
if err != nil {
return false
}
if found {
return true
}
found, _, err = readAllImageStores(s, func(store roImageStore) (bool, bool, error) {
if store.Exists(id) {
return true, true, nil
}
return false, false, nil
})
if err != nil {
return false
}
if found {
return true
}
found, _, err = readContainerStore(s, func() (bool, bool, error) {
return s.containerStore.Exists(id), true, nil
})
if err != nil {
return false
}
return found
}
func dedupeStrings(names []string) []string {
seen := make(map[string]struct{})
deduped := make([]string, 0, len(names))
for _, name := range names {
if _, wasSeen := seen[name]; !wasSeen {
seen[name] = struct{}{}
deduped = append(deduped, name)
}
}
return deduped
}
func dedupeDigests(digests []digest.Digest) []digest.Digest {
seen := make(map[digest.Digest]struct{})
deduped := make([]digest.Digest, 0, len(digests))
for _, d := range digests {
if _, wasSeen := seen[d]; !wasSeen {
seen[d] = struct{}{}
deduped = append(deduped, d)
}
}
return deduped
}
// Deprecated: Prone to race conditions, suggested alternatives are `AddNames` and `RemoveNames`.
func (s *store) SetNames(id string, names []string) error {
return s.updateNames(id, names, setNames)
}
func (s *store) AddNames(id string, names []string) error {
return s.updateNames(id, names, addNames)
}
func (s *store) RemoveNames(id string, names []string) error {
return s.updateNames(id, names, removeNames)
}
func (s *store) updateNames(id string, names []string, op updateNameOperation) error {
deduped := dedupeStrings(names)
if found, err := writeToLayerStore(s, func(rlstore rwLayerStore) (bool, error) {
if !rlstore.Exists(id) {
return false, nil
}
return true, rlstore.updateNames(id, deduped, op)
}); err != nil || found {
return err
}
if err := s.imageStore.startWriting(); err != nil {
return err
}
defer s.imageStore.stopWriting()
if s.imageStore.Exists(id) {
return s.imageStore.updateNames(id, deduped, op)
}
// Check if the id refers to a read-only image store -- we want to allow images in
// read-only stores to have their names changed.
for _, is := range s.roImageStores {
store := is
if err := store.startReading(); err != nil {
return err
}
defer store.stopReading()
if i, err := store.Get(id); err == nil {
// "pull up" the image so that we can change its names list
options := ImageOptions{
CreationDate: i.Created,
Digest: i.Digest,
Digests: copySlicePreferringNil(i.Digests),
Metadata: i.Metadata,
NamesHistory: copySlicePreferringNil(i.NamesHistory),
Flags: copyMapPreferringNil(i.Flags),
}
for _, key := range i.BigDataNames {
data, err := store.BigData(id, key)
if err != nil {
return err
}
dataDigest, err := store.BigDataDigest(id, key)
if err != nil {
return err
}
options.BigData = append(options.BigData, ImageBigDataOption{
Key: key,
Data: data,
Digest: dataDigest,
})
}
_, err = s.imageStore.create(id, i.Names, i.TopLayer, options)
if err != nil {
return err
}
// now make the changes to the writeable image record's names list
return s.imageStore.updateNames(id, deduped, op)
}
}
if found, err := writeToContainerStore(s, func() (bool, error) {
if !s.containerStore.Exists(id) {
return false, nil
}
return true, s.containerStore.updateNames(id, deduped, op)
}); err != nil || found {
return err
}
return ErrLayerUnknown
}
func (s *store) Names(id string) ([]string, error) {
if res, done, err := readAllLayerStores(s, func(store roLayerStore) ([]string, bool, error) {
if l, err := store.Get(id); l != nil && err == nil {
return l.Names, true, nil
}
return nil, false, nil
}); done {
return res, err
}
if res, done, err := readAllImageStores(s, func(store roImageStore) ([]string, bool, error) {
if i, err := store.Get(id); i != nil && err == nil {
return i.Names, true, nil
}
return nil, false, nil
}); done {
return res, err
}
if res, done, err := readContainerStore(s, func() ([]string, bool, error) {
if c, err := s.containerStore.Get(id); c != nil && err == nil {
return c.Names, true, nil
}
return nil, false, nil
}); done {
return res, err
}
return nil, ErrLayerUnknown
}
func (s *store) Lookup(name string) (string, error) {
if res, done, err := readAllLayerStores(s, func(store roLayerStore) (string, bool, error) {
if l, err := store.Get(name); l != nil && err == nil {
return l.ID, true, nil
}
return "", false, nil
}); done {
return res, err
}
if res, done, err := readAllImageStores(s, func(store roImageStore) (string, bool, error) {
if i, err := store.Get(name); i != nil && err == nil {
return i.ID, true, nil
}
return "", false, nil
}); done {
return res, err
}
if res, done, err := readContainerStore(s, func() (string, bool, error) {
if c, err := s.containerStore.Get(name); c != nil && err == nil {
return c.ID, true, nil
}
return "", false, nil
}); done {
return res, err
}
return "", ErrLayerUnknown
}
func (s *store) DeleteLayer(id string) error {
return s.writeToAllStores(func(rlstore rwLayerStore) error {
if rlstore.Exists(id) {
if l, err := rlstore.Get(id); err != nil {
id = l.ID
}
layers, err := rlstore.Layers()
if err != nil {
return err
}
for _, layer := range layers {
if layer.Parent == id {
return fmt.Errorf("used by layer %v: %w", layer.ID, ErrLayerHasChildren)
}
}
images, err := s.imageStore.Images()
if err != nil {
return err
}
for _, image := range images {
if image.TopLayer == id {
return fmt.Errorf("layer %v used by image %v: %w", id, image.ID, ErrLayerUsedByImage)
}
}
containers, err := s.containerStore.Containers()
if err != nil {
return err
}
for _, container := range containers {
if container.LayerID == id {
return fmt.Errorf("layer %v used by container %v: %w", id, container.ID, ErrLayerUsedByContainer)
}
}
if err := rlstore.Delete(id); err != nil {
return fmt.Errorf("delete layer %v: %w", id, err)
}
for _, image := range images {
if stringutils.InSlice(image.MappedTopLayers, id) {
if err = s.imageStore.removeMappedTopLayer(image.ID, id); err != nil {
return fmt.Errorf("remove mapped top layer %v from image %v: %w", id, image.ID, err)
}
}
}
return nil
}
return ErrNotALayer
})
}
func (s *store) DeleteImage(id string, commit bool) (layers []string, err error) {
layersToRemove := []string{}
if err := s.writeToAllStores(func(rlstore rwLayerStore) error {
// Delete image from all available imagestores configured to be used.
imageFound := false
for _, is := range s.rwImageStores {
if is != s.imageStore {
// This is an additional writeable image store
// so we must perform lock
if err := is.startWriting(); err != nil {
return err
}
defer is.stopWriting()
}
if !is.Exists(id) {
continue
}
imageFound = true
image, err := is.Get(id)
if err != nil {
return err
}
id = image.ID
containers, err := s.containerStore.Containers()
if err != nil {
return err
}
aContainerByImage := make(map[string]string)
for _, container := range containers {
aContainerByImage[container.ImageID] = container.ID
}
if container, ok := aContainerByImage[id]; ok {
return fmt.Errorf("image used by %v: %w", container, ErrImageUsedByContainer)
}
images, err := is.Images()
if err != nil {
return err
}
layers, err := rlstore.Layers()
if err != nil {
return err
}
childrenByParent := make(map[string][]string)
for _, layer := range layers {
childrenByParent[layer.Parent] = append(childrenByParent[layer.Parent], layer.ID)
}
otherImagesTopLayers := make(map[string]struct{})
for _, img := range images {
if img.ID != id {
otherImagesTopLayers[img.TopLayer] = struct{}{}
for _, layerID := range img.MappedTopLayers {
otherImagesTopLayers[layerID] = struct{}{}
}
}
}
if commit {
if err = is.Delete(id); err != nil {
return err
}
}
layer := image.TopLayer
layersToRemoveMap := make(map[string]struct{})
layersToRemove = append(layersToRemove, image.MappedTopLayers...)
for _, mappedTopLayer := range image.MappedTopLayers {
layersToRemoveMap[mappedTopLayer] = struct{}{}
}
for layer != "" {
if s.containerStore.Exists(layer) {
break
}
if _, used := otherImagesTopLayers[layer]; used {
break
}
parent := ""
if l, err := rlstore.Get(layer); err == nil {
parent = l.Parent
}
hasChildrenNotBeingRemoved := func() bool {
layersToCheck := []string{layer}
if layer == image.TopLayer {
layersToCheck = append(layersToCheck, image.MappedTopLayers...)
}
for _, layer := range layersToCheck {
if childList := childrenByParent[layer]; len(childList) > 0 {
for _, child := range childList {
if _, childIsSlatedForRemoval := layersToRemoveMap[child]; childIsSlatedForRemoval {
continue
}
return true
}
}
}
return false
}
if hasChildrenNotBeingRemoved() {
break
}
layersToRemove = append(layersToRemove, layer)
layersToRemoveMap[layer] = struct{}{}
layer = parent
}
}
if !imageFound {
return ErrNotAnImage
}
if commit {
for _, layer := range layersToRemove {
if err = rlstore.Delete(layer); err != nil {
return err
}
}
}
return nil
}); err != nil {
return nil, err
}
return layersToRemove, nil
}
func (s *store) DeleteContainer(id string) error {
return s.writeToAllStores(func(rlstore rwLayerStore) error {
if !s.containerStore.Exists(id) {
return ErrNotAContainer
}
container, err := s.containerStore.Get(id)
if err != nil {
return ErrNotAContainer
}
// delete the layer first, separately, so that if we get an
// error while trying to do so, we don't go ahead and delete
// the container record that refers to it, effectively losing
// track of it
if rlstore.Exists(container.LayerID) {
if err := rlstore.Delete(container.LayerID); err != nil {
return err
}
}
var wg errgroup.Group
middleDir := s.graphDriverName + "-containers"
wg.Go(func() error {
gcpath := filepath.Join(s.GraphRoot(), middleDir, container.ID)
return system.EnsureRemoveAll(gcpath)
})
wg.Go(func() error {
rcpath := filepath.Join(s.RunRoot(), middleDir, container.ID)
return system.EnsureRemoveAll(rcpath)
})
if multierr := wg.Wait(); multierr != nil {
return multierr
}
return s.containerStore.Delete(id)
})
}
func (s *store) Delete(id string) error {
return s.writeToAllStores(func(rlstore rwLayerStore) error {
if s.containerStore.Exists(id) {
if container, err := s.containerStore.Get(id); err == nil {
if rlstore.Exists(container.LayerID) {
if err = rlstore.Delete(container.LayerID); err != nil {
return err
}
if err = s.containerStore.Delete(id); err != nil {
return err
}
middleDir := s.graphDriverName + "-containers"
gcpath := filepath.Join(s.GraphRoot(), middleDir, container.ID, "userdata")
if err = os.RemoveAll(gcpath); err != nil {
return err
}
rcpath := filepath.Join(s.RunRoot(), middleDir, container.ID, "userdata")
if err = os.RemoveAll(rcpath); err != nil {
return err
}
return nil
}
return ErrNotALayer
}
}
if s.imageStore.Exists(id) {
return s.imageStore.Delete(id)
}
if rlstore.Exists(id) {
return rlstore.Delete(id)
}
return ErrLayerUnknown
})
}
func (s *store) Wipe() error {
return s.writeToAllStores(func(rlstore rwLayerStore) error {
if err := s.containerStore.Wipe(); err != nil {
return err
}
if err := s.imageStore.Wipe(); err != nil {
return err
}
return rlstore.Wipe()
})
}
func (s *store) Status() ([][2]string, error) {
rlstore, err := s.getLayerStore()
if err != nil {
return nil, err
}
return rlstore.Status()
}
//go:embed VERSION
var storageVersion string
func (s *store) Version() ([][2]string, error) {
if trimmedVersion := strings.TrimSpace(storageVersion); trimmedVersion != "" {
return [][2]string{{"Version", trimmedVersion}}, nil
}
return [][2]string{}, nil
}
func (s *store) MountImage(id string, mountOpts []string, mountLabel string) (string, error) {
if err := validateMountOptions(mountOpts); err != nil {
return "", err
}
// We need to make sure the home mount is present when the Mount is done, which happens by possibly reinitializing the graph driver
// in startUsingGraphDriver().
if err := s.startUsingGraphDriver(); err != nil {
return "", err
}
defer s.stopUsingGraphDriver()
rlstore, lstores, err := s.bothLayerStoreKindsLocked()
if err != nil {
return "", err
}
var imageHomeStore roImageStore
if err := rlstore.startWriting(); err != nil {
return "", err
}
defer rlstore.stopWriting()
for _, s := range lstores {
if err := s.startReading(); err != nil {
return "", err
}
defer s.stopReading()
}
if err := s.imageStore.startWriting(); err != nil {
return "", err
}
defer s.imageStore.stopWriting()
cimage, err := s.imageStore.Get(id)
if err == nil {
imageHomeStore = s.imageStore
} else {
for _, s := range s.roImageStores {
if err := s.startReading(); err != nil {
return "", err
}
defer s.stopReading()
cimage, err = s.Get(id)
if err == nil {
imageHomeStore = s
break
}
}
}
if cimage == nil {
return "", fmt.Errorf("locating image with ID %q: %w", id, ErrImageUnknown)
}
idmappingsOpts := types.IDMappingOptions{
HostUIDMapping: true,
HostGIDMapping: true,
}
ilayer, err := s.imageTopLayerForMapping(cimage, imageHomeStore, rlstore, lstores, idmappingsOpts)
if err != nil {
return "", err
}
if len(ilayer.UIDMap) > 0 || len(ilayer.GIDMap) > 0 {
return "", fmt.Errorf("cannot create an image with canonical UID/GID mappings in a read-only store")
}
options := drivers.MountOpts{
MountLabel: mountLabel,
Options: append(mountOpts, "ro"),
}
return rlstore.Mount(ilayer.ID, options)
}
func (s *store) Mount(id, mountLabel string) (string, error) {
options := drivers.MountOpts{
MountLabel: mountLabel,
}
// check if `id` is a container, then grab the LayerID, uidmap and gidmap, along with
// otherwise we assume the id is a LayerID and attempt to mount it.
if container, err := s.Container(id); err == nil {
id = container.LayerID
options.UidMaps = container.UIDMap
options.GidMaps = container.GIDMap
options.Options = container.MountOpts()
if !s.disableVolatile {
if v, found := container.Flags[volatileFlag]; found {
if b, ok := v.(bool); ok {
options.Volatile = b
}
}
}
}
// We need to make sure the home mount is present when the Mount is done, which happens by possibly reinitializing the graph driver
// in startUsingGraphDriver().
if err := s.startUsingGraphDriver(); err != nil {
return "", err
}
defer s.stopUsingGraphDriver()
rlstore, lstores, err := s.bothLayerStoreKindsLocked()
if err != nil {
return "", err
}
if options.UidMaps != nil || options.GidMaps != nil {
options.DisableShifting = !s.canUseShifting(options.UidMaps, options.GidMaps)
}
if err := rlstore.startWriting(); err != nil {
return "", err
}
defer rlstore.stopWriting()
if rlstore.Exists(id) {
return rlstore.Mount(id, options)
}
// check if the layer is in a read-only store, and return a better error message
for _, store := range lstores {
if err := store.startReading(); err != nil {
return "", err
}
exists := store.Exists(id)
store.stopReading()
if exists {
return "", fmt.Errorf("mounting read/only store images is not allowed: %w", ErrStoreIsReadOnly)
}
}
return "", ErrLayerUnknown
}
func (s *store) Mounted(id string) (int, error) {
if layerID, err := s.ContainerLayerID(id); err == nil {
id = layerID
}
rlstore, err := s.getLayerStore()
if err != nil {
return 0, err
}
if err := rlstore.startReading(); err != nil {
return 0, err
}
defer rlstore.stopReading()
return rlstore.Mounted(id)
}
func (s *store) UnmountImage(id string, force bool) (bool, error) {
img, err := s.Image(id)
if err != nil {
return false, err
}
return writeToLayerStore(s, func(lstore rwLayerStore) (bool, error) {
for _, layerID := range img.MappedTopLayers {
l, err := lstore.Get(layerID)
if err != nil {
if err == ErrLayerUnknown {
continue
}
return false, err
}
// check if the layer with the canonical mapping is in the mapped top layers
if len(l.UIDMap) == 0 && len(l.GIDMap) == 0 {
return lstore.unmount(l.ID, force, false)
}
}
return lstore.unmount(img.TopLayer, force, false)
})
}
func (s *store) Unmount(id string, force bool) (bool, error) {
if layerID, err := s.ContainerLayerID(id); err == nil {
id = layerID
}
return writeToLayerStore(s, func(rlstore rwLayerStore) (bool, error) {
if rlstore.Exists(id) {
return rlstore.unmount(id, force, false)
}
return false, ErrLayerUnknown
})
}
func (s *store) Changes(from, to string) ([]archive.Change, error) {
// NaiveDiff could cause mounts to happen without a lock, so be safe
// and treat the .Diff operation as a Mount.
// We need to make sure the home mount is present when the Mount is done, which happens by possibly reinitializing the graph driver
// in startUsingGraphDriver().
if err := s.startUsingGraphDriver(); err != nil {
return nil, err
}
defer s.stopUsingGraphDriver()
rlstore, lstores, err := s.bothLayerStoreKindsLocked()
if err != nil {
return nil, err
}
// While the general rules require the layer store to only be locked RO (apart from known LOCKING BUGs)
// the overlay driver requires the primary layer store to be locked RW; see
// drivers/overlay.Driver.getMergedDir.
if err := rlstore.startWriting(); err != nil {
return nil, err
}
if rlstore.Exists(to) {
res, err := rlstore.Changes(from, to)
rlstore.stopWriting()
return res, err
}
rlstore.stopWriting()
for _, s := range lstores {
store := s
if err := store.startReading(); err != nil {
return nil, err
}
if store.Exists(to) {
res, err := store.Changes(from, to)
store.stopReading()
return res, err
}
store.stopReading()
}
return nil, ErrLayerUnknown
}
func (s *store) DiffSize(from, to string) (int64, error) {
if res, done, err := readAllLayerStores(s, func(store roLayerStore) (int64, bool, error) {
if store.Exists(to) {
res, err := store.DiffSize(from, to)
return res, true, err
}
return -1, false, nil
}); done {
if err != nil {
return -1, err
}
return res, nil
}
return -1, ErrLayerUnknown
}
func (s *store) Diff(from, to string, options *DiffOptions) (io.ReadCloser, error) {
// NaiveDiff could cause mounts to happen without a lock, so be safe
// and treat the .Diff operation as a Mount.
// We need to make sure the home mount is present when the Mount is done, which happens by possibly reinitializing the graph driver
// in startUsingGraphDriver().
if err := s.startUsingGraphDriver(); err != nil {
return nil, err
}
defer s.stopUsingGraphDriver()
rlstore, lstores, err := s.bothLayerStoreKindsLocked()
if err != nil {
return nil, err
}
// While the general rules require the layer store to only be locked RO (apart from known LOCKING BUGs)
// the overlay driver requires the primary layer store to be locked RW; see
// drivers/overlay.Driver.getMergedDir.
if err := rlstore.startWriting(); err != nil {
return nil, err
}
if rlstore.Exists(to) {
rc, err := rlstore.Diff(from, to, options)
if rc != nil && err == nil {
wrapped := ioutils.NewReadCloserWrapper(rc, func() error {
err := rc.Close()
rlstore.stopWriting()
return err
})
return wrapped, nil
}
rlstore.stopWriting()
return rc, err
}
rlstore.stopWriting()
for _, s := range lstores {
store := s
if err := store.startReading(); err != nil {
return nil, err
}
if store.Exists(to) {
rc, err := store.Diff(from, to, options)
if rc != nil && err == nil {
wrapped := ioutils.NewReadCloserWrapper(rc, func() error {
err := rc.Close()
store.stopReading()
return err
})
return wrapped, nil
}
store.stopReading()
return rc, err
}
store.stopReading()
}
return nil, ErrLayerUnknown
}
func (s *store) ApplyStagedLayer(args ApplyStagedLayerOptions) (*Layer, error) {
defer func() {
if args.DiffOutput.TarSplit != nil {
args.DiffOutput.TarSplit.Close()
args.DiffOutput.TarSplit = nil
}
}()
rlstore, rlstores, err := s.bothLayerStoreKinds()
if err != nil {
return nil, err
}
if err := rlstore.startWriting(); err != nil {
return nil, err
}
defer rlstore.stopWriting()
layer, err := rlstore.Get(args.ID)
if err != nil && !errors.Is(err, ErrLayerUnknown) {
return layer, err
}
if err == nil {
// This code path exists only for cmd/containers/storage.applyDiffUsingStagingDirectory; we have tests that
// assume layer creation and applying a staged layer are separate steps. Production pull code always uses the
// other path, where layer creation is atomic.
return layer, rlstore.applyDiffFromStagingDirectory(args.ID, args.DiffOutput, args.DiffOptions)
}
// if the layer doesn't exist yet, try to create it.
slo := stagedLayerOptions{
DiffOutput: args.DiffOutput,
DiffOptions: args.DiffOptions,
}
layer, _, err = s.putLayer(rlstore, rlstores, args.ID, args.ParentLayer, args.Names, args.MountLabel, args.Writeable, args.LayerOptions, nil, &slo)
return layer, err
}
func (s *store) CleanupStagedLayer(diffOutput *drivers.DriverWithDifferOutput) error {
if diffOutput.TarSplit != nil {
diffOutput.TarSplit.Close()
diffOutput.TarSplit = nil
}
_, err := writeToLayerStore(s, func(rlstore rwLayerStore) (struct{}, error) {
return struct{}{}, rlstore.CleanupStagingDirectory(diffOutput.Target)
})
return err
}
func (s *store) PrepareStagedLayer(options *drivers.ApplyDiffWithDifferOpts, differ drivers.Differ) (*drivers.DriverWithDifferOutput, error) {
rlstore, err := s.getLayerStore()
if err != nil {
return nil, err
}
return rlstore.applyDiffWithDifferNoLock(options, differ)
}
func (s *store) DifferTarget(id string) (string, error) {
return writeToLayerStore(s, func(rlstore rwLayerStore) (string, error) {
if rlstore.Exists(id) {
return rlstore.DifferTarget(id)
}
return "", ErrLayerUnknown
})
}
func (s *store) ApplyDiff(to string, diff io.Reader) (int64, error) {
return writeToLayerStore(s, func(rlstore rwLayerStore) (int64, error) {
if rlstore.Exists(to) {
return rlstore.ApplyDiff(to, diff)
}
return -1, ErrLayerUnknown
})
}
func (s *store) layersByMappedDigest(m func(roLayerStore, digest.Digest) ([]Layer, error), d digest.Digest) ([]Layer, error) {
var layers []Layer
if _, _, err := readAllLayerStores(s, func(store roLayerStore) (struct{}, bool, error) {
storeLayers, err := m(store, d)
if err != nil {
if !errors.Is(err, ErrLayerUnknown) {
return struct{}{}, true, err
}
return struct{}{}, false, nil
}
layers = append(layers, storeLayers...)
return struct{}{}, false, nil
}); err != nil {
return nil, err
}
if len(layers) == 0 {
return nil, ErrLayerUnknown
}
return layers, nil
}
func (s *store) LayersByCompressedDigest(d digest.Digest) ([]Layer, error) {
if err := d.Validate(); err != nil {
return nil, fmt.Errorf("looking for compressed layers matching digest %q: %w", d, err)
}
return s.layersByMappedDigest(func(r roLayerStore, d digest.Digest) ([]Layer, error) { return r.LayersByCompressedDigest(d) }, d)
}
func (s *store) LayersByUncompressedDigest(d digest.Digest) ([]Layer, error) {
if err := d.Validate(); err != nil {
return nil, fmt.Errorf("looking for layers matching digest %q: %w", d, err)
}
return s.layersByMappedDigest(func(r roLayerStore, d digest.Digest) ([]Layer, error) { return r.LayersByUncompressedDigest(d) }, d)
}
func (s *store) LayersByTOCDigest(d digest.Digest) ([]Layer, error) {
if err := d.Validate(); err != nil {
return nil, fmt.Errorf("looking for TOC matching digest %q: %w", d, err)
}
return s.layersByMappedDigest(func(r roLayerStore, d digest.Digest) ([]Layer, error) { return r.LayersByTOCDigest(d) }, d)
}
func (s *store) LayerSize(id string) (int64, error) {
if res, done, err := readAllLayerStores(s, func(store roLayerStore) (int64, bool, error) {
if store.Exists(id) {
res, err := store.Size(id)
return res, true, err
}
return -1, false, nil
}); done {
if err != nil {
return -1, err
}
return res, nil
}
return -1, ErrLayerUnknown
}
func (s *store) LayerParentOwners(id string) ([]int, []int, error) {
rlstore, err := s.getLayerStore()
if err != nil {
return nil, nil, err
}
if err := rlstore.startReading(); err != nil {
return nil, nil, err
}
defer rlstore.stopReading()
if rlstore.Exists(id) {
return rlstore.ParentOwners(id)
}
return nil, nil, ErrLayerUnknown
}
func (s *store) ContainerParentOwners(id string) ([]int, []int, error) {
rlstore, err := s.getLayerStore()
if err != nil {
return nil, nil, err
}
if err := rlstore.startReading(); err != nil {
return nil, nil, err
}
defer rlstore.stopReading()
if err := s.containerStore.startReading(); err != nil {
return nil, nil, err
}
defer s.containerStore.stopReading()
container, err := s.containerStore.Get(id)
if err != nil {
return nil, nil, err
}
if rlstore.Exists(container.LayerID) {
return rlstore.ParentOwners(container.LayerID)
}
return nil, nil, ErrLayerUnknown
}
func (s *store) Layers() ([]Layer, error) {
var layers []Layer
if _, done, err := readAllLayerStores(s, func(store roLayerStore) (struct{}, bool, error) {
storeLayers, err := store.Layers()
if err != nil {
return struct{}{}, true, err
}
layers = append(layers, storeLayers...)
return struct{}{}, false, nil
}); done {
return nil, err
}
return layers, nil
}
func (s *store) Images() ([]Image, error) {
var images []Image
if _, _, err := readAllImageStores(s, func(store roImageStore) (struct{}, bool, error) {
storeImages, err := store.Images()
if err != nil {
return struct{}{}, true, err
}
images = append(images, storeImages...)
return struct{}{}, false, nil
}); err != nil {
return nil, err
}
return images, nil
}
func (s *store) Containers() ([]Container, error) {
res, _, err := readContainerStore(s, func() ([]Container, bool, error) {
res, err := s.containerStore.Containers()
return res, true, err
})
return res, err
}
func (s *store) Layer(id string) (*Layer, error) {
if res, done, err := readAllLayerStores(s, func(store roLayerStore) (*Layer, bool, error) {
layer, err := store.Get(id)
if err == nil {
return layer, true, nil
}
return nil, false, nil
}); done {
return res, err
}
return nil, ErrLayerUnknown
}
func (s *store) LookupAdditionalLayer(tocDigest digest.Digest, imageref string) (AdditionalLayer, error) {
var adriver drivers.AdditionalLayerStoreDriver
if err := func() error { // A scope for defer
if err := s.startUsingGraphDriver(); err != nil {
return err
}
defer s.stopUsingGraphDriver()
a, ok := s.graphDriver.(drivers.AdditionalLayerStoreDriver)
if !ok {
return ErrLayerUnknown
}
adriver = a
return nil
}(); err != nil {
return nil, err
}
al, err := adriver.LookupAdditionalLayer(tocDigest, imageref)
if err != nil {
if errors.Is(err, drivers.ErrLayerUnknown) {
return nil, ErrLayerUnknown
}
return nil, err
}
info, err := al.Info()
if err != nil {
return nil, err
}
defer info.Close()
var layer Layer
if err := json.NewDecoder(info).Decode(&layer); err != nil {
return nil, err
}
return &additionalLayer{&layer, al, s}, nil
}
type additionalLayer struct {
layer *Layer
handler drivers.AdditionalLayer
s *store
}
func (al *additionalLayer) TOCDigest() digest.Digest {
return al.layer.TOCDigest
}
func (al *additionalLayer) CompressedSize() int64 {
return al.layer.CompressedSize
}
func (al *additionalLayer) PutAs(id, parent string, names []string) (*Layer, error) {
rlstore, rlstores, err := al.s.bothLayerStoreKinds()
if err != nil {
return nil, err
}
if err := rlstore.startWriting(); err != nil {
return nil, err
}
defer rlstore.stopWriting()
var parentLayer *Layer
if parent != "" {
for _, lstore := range append([]roLayerStore{rlstore}, rlstores...) {
if lstore != rlstore {
if err := lstore.startReading(); err != nil {
return nil, err
}
defer lstore.stopReading()
}
parentLayer, err = lstore.Get(parent)
if err == nil {
break
}
}
if parentLayer == nil {
return nil, ErrLayerUnknown
}
}
return rlstore.PutAdditionalLayer(id, parentLayer, names, al.handler)
}
func (al *additionalLayer) Release() {
al.handler.Release()
}
func (s *store) Image(id string) (*Image, error) {
if res, done, err := readAllImageStores(s, func(store roImageStore) (*Image, bool, error) {
image, err := store.Get(id)
if err == nil {
if store != s.imageStore {
// found it in a read-only store - readAllImageStores() still has the writeable store locked for reading
if _, localErr := s.imageStore.Get(image.ID); localErr == nil {
// if the lookup key was a name, and we found the image in a read-only
// store, but we have an entry with the same ID in the read-write store,
// then the name was removed when we duplicated the image's
// record into writable storage, so we should ignore this entry
return nil, false, nil
}
}
return image, true, nil
}
return nil, false, nil
}); done {
return res, err
}
return nil, fmt.Errorf("locating image with ID %q: %w", id, ErrImageUnknown)
}
func (s *store) ImagesByTopLayer(id string) ([]*Image, error) {
layer, err := s.Layer(id)
if err != nil {
return nil, err
}
images := []*Image{}
if _, _, err := readAllImageStores(s, func(store roImageStore) (struct{}, bool, error) {
imageList, err := store.Images()
if err != nil {
return struct{}{}, true, err
}
for _, image := range imageList {
image := image
if image.TopLayer == layer.ID || stringutils.InSlice(image.MappedTopLayers, layer.ID) {
images = append(images, &image)
}
}
return struct{}{}, false, nil
}); err != nil {
return nil, err
}
return images, nil
}
func (s *store) ImagesByDigest(d digest.Digest) ([]*Image, error) {
images := []*Image{}
if _, _, err := readAllImageStores(s, func(store roImageStore) (struct{}, bool, error) {
imageList, err := store.ByDigest(d)
if err != nil && !errors.Is(err, ErrImageUnknown) {
return struct{}{}, true, err
}
images = append(images, imageList...)
return struct{}{}, false, nil
}); err != nil {
return nil, err
}
return images, nil
}
func (s *store) Container(id string) (*Container, error) {
res, _, err := readContainerStore(s, func() (*Container, bool, error) {
res, err := s.containerStore.Get(id)
return res, true, err
})
return res, err
}
func (s *store) ContainerLayerID(id string) (string, error) {
container, _, err := readContainerStore(s, func() (*Container, bool, error) {
res, err := s.containerStore.Get(id)
return res, true, err
})
if err != nil {
return "", err
}
return container.LayerID, nil
}
func (s *store) ContainerByLayer(id string) (*Container, error) {
layer, err := s.Layer(id)
if err != nil {
return nil, err
}
containerList, _, err := readContainerStore(s, func() ([]Container, bool, error) {
res, err := s.containerStore.Containers()
return res, true, err
})
if err != nil {
return nil, err
}
for _, container := range containerList {
if container.LayerID == layer.ID {
return &container, nil
}
}
return nil, ErrContainerUnknown
}
func (s *store) ImageDirectory(id string) (string, error) {
foundImage := false
if res, done, err := readAllImageStores(s, func(store roImageStore) (string, bool, error) {
if store.Exists(id) {
foundImage = true
}
middleDir := s.graphDriverName + "-images"
gipath := filepath.Join(s.GraphRoot(), middleDir, id, "userdata")
if err := os.MkdirAll(gipath, 0o700); err != nil {
return "", true, err
}
return gipath, true, nil
}); done {
return res, err
}
if foundImage {
return "", fmt.Errorf("locating image with ID %q (consider removing the image to resolve the issue): %w", id, os.ErrNotExist)
}
return "", fmt.Errorf("locating image with ID %q: %w", id, ErrImageUnknown)
}
func (s *store) ContainerDirectory(id string) (string, error) {
res, _, err := readContainerStore(s, func() (string, bool, error) {
id, err := s.containerStore.Lookup(id)
if err != nil {
return "", true, err
}
middleDir := s.graphDriverName + "-containers"
gcpath := filepath.Join(s.GraphRoot(), middleDir, id, "userdata")
if err := os.MkdirAll(gcpath, 0o700); err != nil {
return "", true, err
}
return gcpath, true, nil
})
return res, err
}
func (s *store) ImageRunDirectory(id string) (string, error) {
foundImage := false
if res, done, err := readAllImageStores(s, func(store roImageStore) (string, bool, error) {
if store.Exists(id) {
foundImage = true
}
middleDir := s.graphDriverName + "-images"
rcpath := filepath.Join(s.RunRoot(), middleDir, id, "userdata")
if err := os.MkdirAll(rcpath, 0o700); err != nil {
return "", true, err
}
return rcpath, true, nil
}); done {
return res, err
}
if foundImage {
return "", fmt.Errorf("locating image with ID %q (consider removing the image to resolve the issue): %w", id, os.ErrNotExist)
}
return "", fmt.Errorf("locating image with ID %q: %w", id, ErrImageUnknown)
}
func (s *store) ContainerRunDirectory(id string) (string, error) {
res, _, err := readContainerStore(s, func() (string, bool, error) {
id, err := s.containerStore.Lookup(id)
if err != nil {
return "", true, err
}
middleDir := s.graphDriverName + "-containers"
rcpath := filepath.Join(s.RunRoot(), middleDir, id, "userdata")
if err := os.MkdirAll(rcpath, 0o700); err != nil {
return "", true, err
}
return rcpath, true, nil
})
return res, err
}
func (s *store) SetContainerDirectoryFile(id, file string, data []byte) error {
dir, err := s.ContainerDirectory(id)
if err != nil {
return err
}
err = os.MkdirAll(filepath.Dir(filepath.Join(dir, file)), 0o700)
if err != nil {
return err
}
return ioutils.AtomicWriteFile(filepath.Join(dir, file), data, 0o600)
}
func (s *store) FromContainerDirectory(id, file string) ([]byte, error) {
dir, err := s.ContainerDirectory(id)
if err != nil {
return nil, err
}
return os.ReadFile(filepath.Join(dir, file))
}
func (s *store) SetContainerRunDirectoryFile(id, file string, data []byte) error {
dir, err := s.ContainerRunDirectory(id)
if err != nil {
return err
}
err = os.MkdirAll(filepath.Dir(filepath.Join(dir, file)), 0o700)
if err != nil {
return err
}
return ioutils.AtomicWriteFile(filepath.Join(dir, file), data, 0o600)
}
func (s *store) FromContainerRunDirectory(id, file string) ([]byte, error) {
dir, err := s.ContainerRunDirectory(id)
if err != nil {
return nil, err
}
return os.ReadFile(filepath.Join(dir, file))
}
func (s *store) Shutdown(force bool) ([]string, error) {
mounted := []string{}
if err := s.startUsingGraphDriver(); err != nil {
return mounted, err
}
defer s.stopUsingGraphDriver()
rlstore, err := s.getLayerStoreLocked()
if err != nil {
return mounted, err
}
if err := rlstore.startWriting(); err != nil {
return nil, err
}
defer rlstore.stopWriting()
layers, err := rlstore.Layers()
if err != nil {
return mounted, err
}
for _, layer := range layers {
if layer.MountCount == 0 {
continue
}
mounted = append(mounted, layer.ID)
if force {
for {
_, err2 := rlstore.unmount(layer.ID, force, true)
if err2 == ErrLayerNotMounted {
break
}
if err2 != nil {
if err == nil {
err = err2
}
break
}
}
}
}
if len(mounted) > 0 && err == nil {
err = fmt.Errorf("a layer is mounted: %w", ErrLayerUsedByContainer)
}
if err == nil {
// We dont retain the lastWrite value, and treat this update as if someone else did the .Cleanup(),
// so that we reload after a .Shutdown() the same way other processes would.
// Shutdown() is basically an error path, so reliability is more important than performance.
if _, err2 := s.graphLock.RecordWrite(); err2 != nil {
err = fmt.Errorf("graphLock.RecordWrite failed: %w", err2)
}
// Do the Cleanup() only after we are sure that the change was recorded with RecordWrite(), so that
// the next user picks it.
if err == nil {
err = s.graphDriver.Cleanup()
}
}
return mounted, err
}
// Convert a BigData key name into an acceptable file name.
func makeBigDataBaseName(key string) string {
reader := strings.NewReader(key)
for reader.Len() > 0 {
ch, size, err := reader.ReadRune()
if err != nil || size != 1 {
break
}
if ch != '.' && (ch < '0' || ch > '9') && (ch < 'a' || ch > 'z') {
break
}
}
if reader.Len() > 0 {
return "=" + base64.StdEncoding.EncodeToString([]byte(key))
}
return key
}
func stringSliceWithoutValue(slice []string, value string) []string {
return slices.DeleteFunc(slices.Clone(slice), func(v string) bool {
return v == value
})
}
// copySlicePreferringNil returns a copy of the slice.
// If s is empty, a nil is returned.
func copySlicePreferringNil[S ~[]E, E any](s S) S {
if len(s) == 0 {
return nil
}
return slices.Clone(s)
}
// copyMapPreferringNil returns a shallow clone of map m.
// If m is empty, a nil is returned.
//
// (As of, e.g., Go 1.23, maps.Clone preserves nil, but thats not a documented promise;
// and this function turns even non-nil empty maps into nil.)
func copyMapPreferringNil[K comparable, V any](m map[K]V) map[K]V {
if len(m) == 0 {
return nil
}
return maps.Clone(m)
}
// newMapFrom returns a shallow clone of map m.
// If m is empty, an empty map is allocated and returned.
func newMapFrom[K comparable, V any](m map[K]V) map[K]V {
if len(m) == 0 {
return make(map[K]V, 0)
}
return maps.Clone(m)
}
func copyImageBigDataOptionSlice(slice []ImageBigDataOption) []ImageBigDataOption {
ret := make([]ImageBigDataOption, len(slice))
for i := range slice {
ret[i].Key = slice[i].Key
ret[i].Data = slices.Clone(slice[i].Data)
ret[i].Digest = slice[i].Digest
}
return ret
}
func copyContainerBigDataOptionSlice(slice []ContainerBigDataOption) []ContainerBigDataOption {
ret := make([]ContainerBigDataOption, len(slice))
for i := range slice {
ret[i].Key = slice[i].Key
ret[i].Data = slices.Clone(slice[i].Data)
}
return ret
}
// AutoUserNsMinSize is the minimum size for automatically created user namespaces
const AutoUserNsMinSize = 1024
// AutoUserNsMaxSize is the maximum size for automatically created user namespaces
const AutoUserNsMaxSize = 65536
// RootAutoUserNsUser is the default user used for root containers when automatically
// creating a user namespace.
const RootAutoUserNsUser = "containers"
// SetDefaultConfigFilePath sets the default configuration to the specified path, and loads the file.
// Deprecated: Use types.SetDefaultConfigFilePath, which can return an error.
func SetDefaultConfigFilePath(path string) {
_ = types.SetDefaultConfigFilePath(path)
}
// DefaultConfigFile returns the path to the storage config file used
func DefaultConfigFile() (string, error) {
return types.DefaultConfigFile()
}
// ReloadConfigurationFile parses the specified configuration file and overrides
// the configuration in storeOptions.
// Deprecated: Use types.ReloadConfigurationFile, which can return an error.
func ReloadConfigurationFile(configFile string, storeOptions *types.StoreOptions) {
_ = types.ReloadConfigurationFile(configFile, storeOptions)
}
// GetDefaultMountOptions returns the default mountoptions defined in container/storage
func GetDefaultMountOptions() ([]string, error) {
defaultStoreOptions, err := types.Options()
if err != nil {
return nil, err
}
return GetMountOptions(defaultStoreOptions.GraphDriverName, defaultStoreOptions.GraphDriverOptions)
}
// GetMountOptions returns the mountoptions for the specified driver and graphDriverOptions
func GetMountOptions(driver string, graphDriverOptions []string) ([]string, error) {
mountOpts := []string{
".mountopt",
fmt.Sprintf("%s.mountopt", driver),
}
for _, option := range graphDriverOptions {
key, val, err := parsers.ParseKeyValueOpt(option)
if err != nil {
return nil, err
}
key = strings.ToLower(key)
if slices.Contains(mountOpts, key) {
return strings.Split(val, ","), nil
}
}
return nil, nil
}
// Free removes the store from the list of stores
func (s *store) Free() {
if i := slices.Index(stores, s); i != -1 {
stores = slices.Delete(stores, i, i+1)
}
}
// Tries to clean up old unreferenced container leftovers. returns the first error
// but continues as far as it can
func (s *store) GarbageCollect() error {
_, firstErr := writeToContainerStore(s, func() (struct{}, error) {
return struct{}{}, s.containerStore.GarbageCollect()
})
_, moreErr := writeToImageStore(s, func() (struct{}, error) {
return struct{}{}, s.imageStore.GarbageCollect()
})
if firstErr == nil {
firstErr = moreErr
}
_, moreErr = writeToLayerStore(s, func(rlstore rwLayerStore) (struct{}, error) {
return struct{}{}, rlstore.GarbageCollect()
})
if firstErr == nil {
firstErr = moreErr
}
return firstErr
}
// List returns a MultiListResult structure that contains layer, image, or container
// extracts according to the values in MultiListOptions.
func (s *store) MultiList(options MultiListOptions) (MultiListResult, error) {
// TODO: Possible optimization: Deduplicate content from multiple stores.
out := MultiListResult{}
if options.Layers {
layerStores, err := s.allLayerStores()
if err != nil {
return MultiListResult{}, err
}
for _, roStore := range layerStores {
if err := roStore.startReading(); err != nil {
return MultiListResult{}, err
}
defer roStore.stopReading()
layers, err := roStore.Layers()
if err != nil {
return MultiListResult{}, err
}
out.Layers = append(out.Layers, layers...)
}
}
if options.Images {
for _, roStore := range s.allImageStores() {
if err := roStore.startReading(); err != nil {
return MultiListResult{}, err
}
defer roStore.stopReading()
images, err := roStore.Images()
if err != nil {
return MultiListResult{}, err
}
out.Images = append(out.Images, images...)
}
}
if options.Containers {
containers, _, err := readContainerStore(s, func() ([]Container, bool, error) {
res, err := s.containerStore.Containers()
return res, true, err
})
if err != nil {
return MultiListResult{}, err
}
out.Containers = append(out.Containers, containers...)
}
return out, nil
}
// Dedup deduplicates layers in the store.
func (s *store) Dedup(req DedupArgs) (drivers.DedupResult, error) {
imgs, err := s.Images()
if err != nil {
return drivers.DedupResult{}, err
}
var topLayers []string
for _, i := range imgs {
topLayers = append(topLayers, i.TopLayer)
topLayers = append(topLayers, i.MappedTopLayers...)
}
return writeToLayerStore(s, func(rlstore rwLayerStore) (drivers.DedupResult, error) {
layers := make(map[string]struct{})
for _, i := range topLayers {
cur := i
for cur != "" {
if _, visited := layers[cur]; visited {
break
}
l, err := rlstore.Get(cur)
if err != nil {
if err == ErrLayerUnknown {
break
}
return drivers.DedupResult{}, err
}
layers[cur] = struct{}{}
cur = l.Parent
}
}
r := drivers.DedupArgs{
Options: req.Options,
}
for l := range layers {
r.Layers = append(r.Layers, l)
}
return rlstore.dedup(r)
})
}