package graphdriver import ( "context" "fmt" "io" "os" "path/filepath" "strings" "github.com/containerd/log" "github.com/moby/go-archive" "github.com/moby/sys/user" "github.com/pkg/errors" "github.com/vbatts/tar-split/tar/storage" ) // All registered drivers var drivers = make(map[string]InitFunc) // CreateOpts contains optional arguments for Create() and CreateReadWrite() // methods. type CreateOpts struct { MountLabel string StorageOpt map[string]string } // InitFunc initializes the storage driver. type InitFunc func(root string, options []string, idMap user.IdentityMapping) (Driver, error) // ProtoDriver defines the basic capabilities of a driver. // This interface exists solely to be a minimum set of methods // for client code which choose not to implement the entire Driver // interface and use the NaiveDiffDriver wrapper constructor. // // Use of ProtoDriver directly by client code is not recommended. type ProtoDriver interface { // String returns a string representation of this driver. String() string // CreateReadWrite creates a new, empty filesystem layer that is ready // to be used as the storage for a container. Additional options can // be passed in opts. parent may be "" and opts may be nil. CreateReadWrite(id, parent string, opts *CreateOpts) error // Create creates a new, empty, filesystem layer with the // specified id and parent and options passed in opts. Parent // may be "" and opts may be nil. Create(id, parent string, opts *CreateOpts) error // Remove attempts to remove the filesystem layer with this id. Remove(id string) error // Get returns the mountpoint for the layered filesystem referred // to by this id. You can optionally specify a mountLabel or "". // Returns the absolute path to the mounted layered filesystem. Get(id, mountLabel string) (fs string, err error) // Put releases the system resources for the specified id, // e.g, unmounting layered filesystem. Put(id string) error // Exists returns whether a filesystem layer with the specified // ID exists on this driver. Exists(id string) bool // Status returns a set of key-value pairs which give low // level diagnostic status about this driver. Status() [][2]string // GetMetadata returns a set of key-value pairs which give driver-specific // low-level information about the image/container that the driver is managing. GetMetadata(id string) (map[string]string, error) // Cleanup performs necessary tasks to release resources // held by the driver, e.g., unmounting all layered filesystems // known to this driver. Cleanup() error } // DiffDriver is the interface to use to implement graph diffs type DiffDriver interface { // Diff produces an archive of the changes between the specified // layer and its parent layer which may be "". Diff(id, parent string) (io.ReadCloser, error) // Changes produces a list of changes between the specified layer // and its parent layer. If parent is "", then all changes will be ADD changes. Changes(id, parent string) ([]archive.Change, error) // ApplyDiff extracts the changeset from the given diff into the // layer with the specified id and parent, returning the size of the // new layer in bytes. // The archive.Reader must be an uncompressed stream. ApplyDiff(id, parent string, diff io.Reader) (size int64, err error) // DiffSize calculates the changes between the specified id // and its parent and returns the size in bytes of the changes // relative to its base filesystem directory. DiffSize(id, parent string) (size int64, err error) } // Driver is the interface for layered/snapshot file system drivers. type Driver interface { ProtoDriver DiffDriver } // DiffGetterDriver is the interface for layered file system drivers that // provide a specialized function for getting file contents for tar-split. type DiffGetterDriver interface { Driver // DiffGetter returns an interface to efficiently retrieve the contents // of files in a layer. DiffGetter(id string) (FileGetCloser, error) } // FileGetCloser extends the storage.FileGetter interface with a Close method // for cleaning up. type FileGetCloser interface { storage.FileGetter // Close cleans up any resources associated with the FileGetCloser. Close() error } // Register registers an InitFunc for the driver. func Register(name string, initFunc InitFunc) error { if _, exists := drivers[name]; exists { return errors.Errorf("name already registered %s", name) } drivers[name] = initFunc return nil } // IsRegistered checks to see if the drive with the given name is registered func IsRegistered(name string) bool { _, exists := drivers[name] return exists } // getDriver initializes and returns the registered driver. func getDriver(name string, config Options) (Driver, error) { if initFunc, exists := drivers[name]; exists { return initFunc(filepath.Join(config.Root, name), config.DriverOptions, config.IDMap) } log.G(context.TODO()).WithFields(log.Fields{"driver": name, "home-dir": config.Root}).Error("Failed to GetDriver graph") return nil, ErrNotSupported } // Options is used to initialize a graphdriver type Options struct { Root string DriverOptions []string IDMap user.IdentityMapping ExperimentalEnabled bool } // New creates the driver and initializes it at the specified root. // // It is recommended to pass a name for the driver to use, but If no name // is provided, it attempts to detect the prior storage driver based on // existing state, or otherwise selects a storage driver based on a priority // list and the underlying filesystem. // // It returns an error if the requested storage driver is not supported, // if scanning prior drivers is ambiguous (i.e., if state is found for // multiple drivers), or if no compatible driver is available for the // platform and underlying filesystem. func New(driverName string, config Options) (Driver, error) { ctx := context.TODO() if driverName != "" { log.G(ctx).Infof("[graphdriver] trying configured driver: %s", driverName) if err := checkRemoved(driverName); err != nil { return nil, err } return getDriver(driverName, config) } // Guess for prior driver // // TODO(thaJeztah): move detecting prior drivers separate from New(), and make "name" a required argument. driversMap := scanPriorDrivers(config.Root) priorityList := strings.Split(priority, ",") log.G(ctx).Debugf("[graphdriver] priority list: %v", priorityList) for _, name := range priorityList { if _, prior := driversMap[name]; prior { // of the state found from prior drivers, check in order of our priority // which we would prefer driver, err := getDriver(name, config) if err != nil { // unlike below, we will return error here, because there is prior // state, and now it is no longer supported/prereq/compatible, so // something changed and needs attention. Otherwise the daemon's // images would just "disappear". log.G(ctx).Errorf("[graphdriver] prior storage driver %s failed: %s", name, err) return nil, err } // abort starting when there are other prior configured drivers // to ensure the user explicitly selects the driver to load if len(driversMap) > 1 { var driversSlice []string for d := range driversMap { driversSlice = append(driversSlice, d) } err = errors.Errorf("%s contains several valid graphdrivers: %s; cleanup or explicitly choose storage driver (-s )", config.Root, strings.Join(driversSlice, ", ")) log.G(ctx).Errorf("[graphdriver] %v", err) return nil, err } log.G(ctx).Infof("[graphdriver] using prior storage driver: %s", name) return driver, nil } } // If no prior state was found, continue with automatic selection, and pick // the first supported, non-deprecated, storage driver (in order of priorityList). for _, name := range priorityList { driver, err := getDriver(name, config) if err != nil { if IsDriverNotSupported(err) { continue } return nil, err } return driver, nil } // Check all registered drivers if no priority driver is found for name, initFunc := range drivers { driver, err := initFunc(filepath.Join(config.Root, name), config.DriverOptions, config.IDMap) if err != nil { if IsDriverNotSupported(err) { continue } return nil, err } return driver, nil } return nil, errors.Errorf("no supported storage driver found") } // HasPriorDriver returns true if any prior driver is found func HasPriorDriver(root string) bool { driversMap := scanPriorDrivers(root) return len(driversMap) > 0 } // scanPriorDrivers returns an un-ordered scan of directories of prior storage // drivers. The 'vfs' storage driver is not taken into account, and ignored. func scanPriorDrivers(root string) map[string]bool { driversMap := make(map[string]bool) for driver := range drivers { p := filepath.Join(root, driver) if _, err := os.Stat(p); err == nil && driver != "vfs" { if !isEmptyDir(p) { driversMap[driver] = true } } } return driversMap } // isEmptyDir checks if a directory is empty. It is used to check if prior // storage-driver directories exist. If an error occurs, it also assumes the // directory is not empty (which preserves the behavior _before_ this check // was added) func isEmptyDir(name string) bool { f, err := os.Open(name) if err != nil { return false } defer f.Close() if _, err = f.Readdirnames(1); errors.Is(err, io.EOF) { return true } return false } // checkRemoved checks if a storage-driver has been deprecated (and removed) func checkRemoved(name string) error { switch name { case "aufs", "devicemapper", "overlay": return NotSupportedError(fmt.Sprintf("[graphdriver] ERROR: the %s storage-driver has been deprecated and removed; visit https://docs.docker.com/go/storage-driver/ for more information", name)) } return nil }