containerd/snapshot/btrfs/btrfs.go

260 lines
6.5 KiB
Go
Raw Normal View History

package btrfs
import (
"crypto/sha256"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/docker/containerd"
"github.com/docker/containerd/log"
"github.com/pkg/errors"
"github.com/stevvooe/go-btrfs"
)
type Driver struct {
device string // maybe we can resolve it with path?
root string // root provides paths for internal storage.
}
func NewDriver(device, root string) (*Driver, error) {
return &Driver{device: device, root: root}, nil
}
func (b *Driver) Prepare(key, parent string) ([]containerd.Mount, error) {
return b.makeActive(key, parent, false)
}
func (b *Driver) View(key, parent string) ([]containerd.Mount, error) {
return b.makeActive(key, parent, true)
}
func (b *Driver) makeActive(key, parent string, readonly bool) ([]containerd.Mount, error) {
var (
active = filepath.Join(b.root, "active")
parents = filepath.Join(b.root, "parents")
snapshots = filepath.Join(b.root, "snapshots")
names = filepath.Join(b.root, "names")
dir = filepath.Join(active, hash(key))
namep = filepath.Join(names, hash(key))
parentlink = filepath.Join(parents, hash(key))
parentp = filepath.Join(snapshots, hash(parent))
)
for _, path := range []string{
active,
parents,
names,
} {
if err := os.MkdirAll(path, 0755); err != nil {
return nil, err
}
}
if parent == "" {
// create new subvolume
// btrfs subvolume create /dir
if err := btrfs.SubvolCreate(dir); err != nil {
return nil, err
}
} else {
// btrfs subvolume snapshot /parent /subvol
if err := btrfs.SubvolSnapshot(dir, parentp, readonly); err != nil {
return nil, err
}
if err := os.Symlink(parentp, parentlink); err != nil {
return nil, err
}
}
if err := ioutil.WriteFile(namep, []byte(key), 0644); err != nil {
return nil, err
}
return b.mounts(dir)
}
func (b *Driver) mounts(dir string) ([]containerd.Mount, error) {
var options []string
// get the subvolume id back out for the mount
info, err := btrfs.SubvolInfo(dir)
if err != nil {
return nil, err
}
options = append(options, fmt.Sprintf("subvolid=%d", info.ID))
if info.Readonly {
options = append(options, "ro")
}
return []containerd.Mount{
{
Type: "btrfs",
Source: b.device, // device?
// NOTE(stevvooe): While it would be nice to use to uuids for
// mounts, they don't work reliably if the uuids are missing.
Options: options,
},
}, nil
}
func (b *Driver) Commit(name, key string) error {
var (
active = filepath.Join(b.root, "active")
snapshots = filepath.Join(b.root, "snapshots")
names = filepath.Join(b.root, "names")
parents = filepath.Join(b.root, "parents")
dir = filepath.Join(active, hash(key))
target = filepath.Join(snapshots, hash(name))
keynamep = filepath.Join(names, hash(key))
namep = filepath.Join(names, hash(name))
keyparentlink = filepath.Join(parents, hash(key))
parentlink = filepath.Join(parents, hash(name))
)
info, err := btrfs.SubvolInfo(dir)
if err != nil {
return err
}
if info.Readonly {
return fmt.Errorf("may not commit view snapshot %q", dir)
}
// look up the parent information to make sure we have the right parent link.
parentp, err := os.Readlink(keyparentlink)
if err != nil {
if !os.IsNotExist(err) {
return err
}
// we have no parent!
}
if err := os.MkdirAll(snapshots, 0755); err != nil {
return err
}
fmt.Println("commit snapshot", target)
if err := btrfs.SubvolSnapshot(target, dir, true); err != nil {
fmt.Println("snapshot error")
return err
}
// remove the key name path as we no longer need it.
if err := os.Remove(keynamep); err != nil {
return err
}
if err := ioutil.WriteFile(namep, []byte(name), 0755); err != nil {
return err
}
if parentp != "" {
// move over the parent link into the commit name.
if err := os.Rename(keyparentlink, parentlink); err != nil {
return err
}
// TODO(stevvooe): For this to not break horribly, we should really
// start taking a full lock. We are going to move all this metadata
// into common storage, so let's not fret over it for now.
}
if err := btrfs.SubvolDelete(dir); err != nil {
return errors.Wrapf(err, "delete subvol failed on %v", dir)
}
return nil
}
// Mounts returns the mounts for the transaction identified by key. Can be
// called on an read-write or readonly transaction.
//
// This can be used to recover mounts after calling View or Prepare.
func (b *Driver) Mounts(key string) ([]containerd.Mount, error) {
dir := filepath.Join(b.root, "active", hash(key))
return b.mounts(dir)
}
// Remove abandons the transaction identified by key. All resources
// associated with the key will be removed.
func (b *Driver) Remove(key string) error {
panic("not implemented")
}
// Parent returns the parent of snapshot identified by name.
func (b *Driver) Parent(name string) (string, error) {
var (
parents = filepath.Join(b.root, "parents")
names = filepath.Join(b.root, "names")
parentlink = filepath.Join(parents, hash(name))
)
parentp, err := os.Readlink(parentlink)
if err != nil {
if !os.IsNotExist(err) {
return "", err
}
return "", nil // no parent!
}
// okay, grab the basename of the parent and look up its name!
parentnamep := filepath.Join(names, filepath.Base(parentp))
p, err := ioutil.ReadFile(parentnamep)
if err != nil {
return "", err
}
return string(p), nil
}
// Exists returns true if the snapshot with name exists.
func (b *Driver) Exists(name string) bool {
target := filepath.Join(b.root, "snapshots", hash(name))
if _, err := os.Stat(target); err != nil {
if !os.IsNotExist(err) {
// TODO(stevvooe): Very rare condition when this fails horribly,
// such as an access error. Ideally, Exists is simple, but we may
// consider returning an error.
log.L.WithError(err).Fatal("error encountered checking for snapshot existence")
}
return false
}
return true
}
// Delete the snapshot idenfitied by name.
//
// If name has children, the operation will fail.
func (b *Driver) Delete(name string) error {
panic("not implemented")
}
// TODO(stevvooe): The methods below are still in flux. We'll need to work
// out the roles of active and committed snapshots for this to become more
// clear.
// Walk the committed snapshots.
func (b *Driver) Walk(fn func(name string) error) error {
panic("not implemented")
}
// Active will call fn for each active transaction.
func (b *Driver) Active(fn func(key string) error) error {
panic("not implemented")
}
func hash(k string) string {
return fmt.Sprintf("%x", sha256.Sum224([]byte(k)))
}