2017-01-13 23:31:21 +00:00
|
|
|
package btrfs
|
2016-12-13 05:53:48 +00:00
|
|
|
|
|
|
|
import (
|
2017-01-13 23:31:21 +00:00
|
|
|
"crypto/sha256"
|
2016-12-13 05:53:48 +00:00
|
|
|
"fmt"
|
2017-02-03 22:20:05 +00:00
|
|
|
"io/ioutil"
|
2016-12-13 05:53:48 +00:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
|
|
|
|
"github.com/docker/containerd"
|
2017-02-03 22:20:05 +00:00
|
|
|
"github.com/docker/containerd/log"
|
|
|
|
"github.com/pkg/errors"
|
2016-12-13 05:53:48 +00:00
|
|
|
"github.com/stevvooe/go-btrfs"
|
|
|
|
)
|
|
|
|
|
2017-02-03 22:20:05 +00:00
|
|
|
type Driver struct {
|
2016-12-13 05:53:48 +00:00
|
|
|
device string // maybe we can resolve it with path?
|
|
|
|
root string // root provides paths for internal storage.
|
|
|
|
}
|
|
|
|
|
2017-02-03 22:20:05 +00:00
|
|
|
func NewDriver(device, root string) (*Driver, error) {
|
|
|
|
return &Driver{device: device, root: root}, nil
|
2016-12-13 05:53:48 +00:00
|
|
|
}
|
|
|
|
|
2017-02-03 22:20:05 +00:00
|
|
|
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)
|
|
|
|
}
|
2016-12-13 05:53:48 +00:00
|
|
|
|
2017-02-03 22:20:05 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2016-12-13 05:53:48 +00:00
|
|
|
|
|
|
|
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
|
2017-02-03 22:20:05 +00:00
|
|
|
if err := btrfs.SubvolSnapshot(dir, parentp, readonly); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := os.Symlink(parentp, parentlink); err != nil {
|
2016-12-13 05:53:48 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-03 22:20:05 +00:00
|
|
|
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
|
|
|
|
|
2016-12-13 05:53:48 +00:00
|
|
|
// get the subvolume id back out for the mount
|
|
|
|
info, err := btrfs.SubvolInfo(dir)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-02-03 22:20:05 +00:00
|
|
|
options = append(options, fmt.Sprintf("subvolid=%d", info.ID))
|
|
|
|
|
|
|
|
if info.Readonly {
|
|
|
|
options = append(options, "ro")
|
|
|
|
}
|
|
|
|
|
2016-12-13 05:53:48 +00:00
|
|
|
return []containerd.Mount{
|
|
|
|
{
|
|
|
|
Type: "btrfs",
|
2017-01-17 08:42:27 +00:00
|
|
|
Source: b.device, // device?
|
2016-12-13 05:53:48 +00:00
|
|
|
// NOTE(stevvooe): While it would be nice to use to uuids for
|
|
|
|
// mounts, they don't work reliably if the uuids are missing.
|
2017-02-03 22:20:05 +00:00
|
|
|
Options: options,
|
2016-12-13 05:53:48 +00:00
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2017-02-03 22:20:05 +00:00
|
|
|
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
|
|
|
|
}
|
2016-12-13 05:53:48 +00:00
|
|
|
|
2017-02-03 22:20:05 +00:00
|
|
|
// remove the key name path as we no longer need it.
|
|
|
|
if err := os.Remove(keynamep); err != nil {
|
2016-12-13 05:53:48 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-02-03 22:20:05 +00:00
|
|
|
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")
|
2016-12-13 05:53:48 +00:00
|
|
|
}
|
2017-01-13 23:31:21 +00:00
|
|
|
|
|
|
|
func hash(k string) string {
|
|
|
|
return fmt.Sprintf("%x", sha256.Sum224([]byte(k)))
|
|
|
|
}
|