daemon/graphdriver/zfs/zfs.go
86d8758e
 // +build linux freebsd solaris
35cdcbb3
 
d5151ca8
 package zfs
 
 import (
 	"fmt"
 	"os"
 	"os/exec"
 	"path"
 	"strconv"
 	"strings"
2cb23527
 	"sync"
d5151ca8
 	"time"
 
2bf73c4b
 	"github.com/Sirupsen/logrus"
d5151ca8
 	"github.com/docker/docker/daemon/graphdriver"
442b4562
 	"github.com/docker/docker/pkg/idtools"
ee00f07e
 	"github.com/docker/docker/pkg/mount"
d5151ca8
 	"github.com/docker/docker/pkg/parsers"
 	zfs "github.com/mistifyio/go-zfs"
abbbf914
 	"github.com/opencontainers/selinux/go-selinux/label"
069fdc8a
 	"golang.org/x/sys/unix"
d5151ca8
 )
 
e27c904b
 type zfsOptions struct {
d5151ca8
 	fsName    string
 	mountPath string
 }
 
 func init() {
 	graphdriver.Register("zfs", Init)
 }
 
927b334e
 // Logger returns a zfs logger implementation.
d5151ca8
 type Logger struct{}
 
e27c904b
 // Log wraps log message from ZFS driver with a prefix '[zfs]'.
d5151ca8
 func (*Logger) Log(cmd []string) {
2bf73c4b
 	logrus.Debugf("[zfs] %s", strings.Join(cmd, " "))
d5151ca8
 }
 
e27c904b
 // Init returns a new ZFS driver.
c1be45fa
 // It takes base mount path and an array of options which are represented as key value pairs.
e27c904b
 // Each option is in the for key=value. 'zfs.fsname' is expected to be a valid key in the options.
442b4562
 func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
d5151ca8
 	var err error
f95b3a6b
 
 	if _, err := exec.LookPath("zfs"); err != nil {
2bf73c4b
 		logrus.Debugf("[zfs] zfs command is not available: %v", err)
f95b3a6b
 		return nil, graphdriver.ErrPrerequisites
 	}
 
 	file, err := os.OpenFile("/dev/zfs", os.O_RDWR, 600)
 	if err != nil {
2bf73c4b
 		logrus.Debugf("[zfs] cannot open /dev/zfs: %v", err)
f95b3a6b
 		return nil, graphdriver.ErrPrerequisites
 	}
 	defer file.Close()
 
d5151ca8
 	options, err := parseOptions(opt)
 	if err != nil {
 		return nil, err
 	}
dd614b5e
 	options.mountPath = base
d5151ca8
 
 	rootdir := path.Dir(base)
 
 	if options.fsName == "" {
 		err = checkRootdirFs(rootdir)
 		if err != nil {
 			return nil, err
 		}
 	}
 
 	if options.fsName == "" {
 		options.fsName, err = lookupZfsDataset(rootdir)
 		if err != nil {
 			return nil, err
 		}
 	}
 
dd614b5e
 	zfs.SetLogger(new(Logger))
d5151ca8
 
bad25ccf
 	filesystems, err := zfs.Filesystems(options.fsName)
d5151ca8
 	if err != nil {
bad25ccf
 		return nil, fmt.Errorf("Cannot find root filesystem %s: %v", options.fsName, err)
 	}
 
 	filesystemsCache := make(map[string]bool, len(filesystems))
 	var rootDataset *zfs.Dataset
 	for _, fs := range filesystems {
 		if fs.Name == options.fsName {
 			rootDataset = fs
 		}
 		filesystemsCache[fs.Name] = true
 	}
 
 	if rootDataset == nil {
416e855e
 		return nil, fmt.Errorf("BUG: zfs get all -t filesystem -rHp '%s' should contain '%s'", options.fsName, options.fsName)
d5151ca8
 	}
 
205bc6fc
 	rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
 	if err != nil {
 		return nil, fmt.Errorf("Failed to get root uid/guid: %v", err)
 	}
 	if err := idtools.MkdirAllAs(base, 0700, rootUID, rootGID); err != nil {
 		return nil, fmt.Errorf("Failed to create '%s': %v", base, err)
 	}
 
5ec8441b
 	if err := mount.MakePrivate(base); err != nil {
 		return nil, err
 	}
bacecabf
 	d := &Driver{
bad25ccf
 		dataset:          rootDataset,
 		options:          options,
 		filesystemsCache: filesystemsCache,
442b4562
 		uidMaps:          uidMaps,
 		gidMaps:          gidMaps,
8bb4d31b
 		ctr:              graphdriver.NewRefCounter(graphdriver.NewDefaultChecker()),
bacecabf
 	}
442b4562
 	return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil
d5151ca8
 }
 
e27c904b
 func parseOptions(opt []string) (zfsOptions, error) {
 	var options zfsOptions
d5151ca8
 	options.fsName = ""
 	for _, option := range opt {
 		key, val, err := parsers.ParseKeyValueOpt(option)
 		if err != nil {
 			return options, err
 		}
 		key = strings.ToLower(key)
 		switch key {
 		case "zfs.fsname":
 			options.fsName = val
 		default:
dd614b5e
 			return options, fmt.Errorf("Unknown option %s", key)
d5151ca8
 		}
 	}
 	return options, nil
 }
 
 func lookupZfsDataset(rootdir string) (string, error) {
069fdc8a
 	var stat unix.Stat_t
 	if err := unix.Stat(rootdir, &stat); err != nil {
d5151ca8
 		return "", fmt.Errorf("Failed to access '%s': %s", rootdir, err)
 	}
 	wantedDev := stat.Dev
 
ee00f07e
 	mounts, err := mount.GetMounts()
dd614b5e
 	if err != nil {
ee00f07e
 		return "", err
d5151ca8
 	}
ee00f07e
 	for _, m := range mounts {
069fdc8a
 		if err := unix.Stat(m.Mountpoint, &stat); err != nil {
2bf73c4b
 			logrus.Debugf("[zfs] failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err)
d5151ca8
 			continue // may fail on fuse file systems
 		}
 
ee00f07e
 		if stat.Dev == wantedDev && m.Fstype == "zfs" {
 			return m.Source, nil
d5151ca8
 		}
 	}
 
dd614b5e
 	return "", fmt.Errorf("Failed to find zfs dataset mounted on '%s' in /proc/mounts", rootdir)
d5151ca8
 }
 
e27c904b
 // Driver holds information about the driver, such as zfs dataset, options and cache.
d5151ca8
 type Driver struct {
bad25ccf
 	dataset          *zfs.Dataset
e27c904b
 	options          zfsOptions
2cb23527
 	sync.Mutex       // protects filesystem cache against concurrent access
bad25ccf
 	filesystemsCache map[string]bool
442b4562
 	uidMaps          []idtools.IDMap
 	gidMaps          []idtools.IDMap
7342060b
 	ctr              *graphdriver.RefCounter
d5151ca8
 }
 
 func (d *Driver) String() string {
 	return "zfs"
 }
 
e27c904b
 // Cleanup is used to implement graphdriver.ProtoDriver. There is no cleanup required for this driver.
d5151ca8
 func (d *Driver) Cleanup() error {
 	return nil
 }
 
e27c904b
 // Status returns information about the ZFS filesystem. It returns a two dimensional array of information
 // such as pool name, dataset name, disk usage, parent quota and compression used.
 // Currently it return 'Zpool', 'Zpool Health', 'Parent Dataset', 'Space Used By Parent',
 // 'Space Available', 'Parent Quota' and 'Compression'.
d5151ca8
 func (d *Driver) Status() [][2]string {
 	parts := strings.Split(d.dataset.Name, "/")
 	pool, err := zfs.GetZpool(parts[0])
 
dd614b5e
 	var poolName, poolHealth string
 	if err == nil {
 		poolName = pool.Name
 		poolHealth = pool.Health
 	} else {
 		poolName = fmt.Sprintf("error while getting pool information %v", err)
 		poolHealth = "not available"
d5151ca8
 	}
dd614b5e
 
 	quota := "no"
 	if d.dataset.Quota != 0 {
d5151ca8
 		quota = strconv.FormatUint(d.dataset.Quota, 10)
 	}
 
 	return [][2]string{
dd614b5e
 		{"Zpool", poolName},
 		{"Zpool Health", poolHealth},
d5151ca8
 		{"Parent Dataset", d.dataset.Name},
 		{"Space Used By Parent", strconv.FormatUint(d.dataset.Used, 10)},
 		{"Space Available", strconv.FormatUint(d.dataset.Avail, 10)},
 		{"Parent Quota", quota},
 		{"Compression", d.dataset.Compression},
 	}
 }
 
15a232fd
 // GetMetadata returns image/container metadata related to graph driver
407a626b
 func (d *Driver) GetMetadata(id string) (map[string]string, error) {
4addda3f
 	return map[string]string{
 		"Mountpoint": d.mountPath(id),
 		"Dataset":    d.zfsPath(id),
 	}, nil
407a626b
 }
 
2cb23527
 func (d *Driver) cloneFilesystem(name, parentName string) error {
d5151ca8
 	snapshotName := fmt.Sprintf("%d", time.Now().Nanosecond())
11e9167a
 	parentDataset := zfs.Dataset{Name: parentName}
d5151ca8
 	snapshot, err := parentDataset.Snapshot(snapshotName /*recursive */, false)
dd614b5e
 	if err != nil {
d5151ca8
 		return err
 	}
 
11e9167a
 	_, err = snapshot.Clone(name, map[string]string{"mountpoint": "legacy"})
2cb23527
 	if err == nil {
 		d.Lock()
 		d.filesystemsCache[name] = true
 		d.Unlock()
 	}
 
d5151ca8
 	if err != nil {
 		snapshot.Destroy(zfs.DestroyDeferDeletion)
 		return err
 	}
dd614b5e
 	return snapshot.Destroy(zfs.DestroyDeferDeletion)
d5151ca8
 }
 
f5f7fee2
 func (d *Driver) zfsPath(id string) string {
d5151ca8
 	return d.options.fsName + "/" + id
 }
 
f5f7fee2
 func (d *Driver) mountPath(id string) string {
112b7e65
 	return path.Join(d.options.mountPath, "graph", getMountpoint(id))
11e9167a
 }
 
ef5bfad3
 // CreateReadWrite creates a layer that is writable for use as a container
 // file system.
b937aa8e
 func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
 	return d.Create(id, parent, opts)
ef5bfad3
 }
 
e27c904b
 // Create prepares the dataset and filesystem for the ZFS driver for the given id under the parent.
b937aa8e
 func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
 	var storageOpt map[string]string
 	if opts != nil {
 		storageOpt = opts.StorageOpt
 	}
 
e9183404
 	err := d.create(id, parent, storageOpt)
d5151ca8
 	if err == nil {
11e9167a
 		return nil
 	}
 	if zfsError, ok := err.(*zfs.Error); ok {
 		if !strings.HasSuffix(zfsError.Stderr, "dataset already exists\n") {
dd614b5e
 			return err
 		}
11e9167a
 		// aborted build -> cleanup
dd614b5e
 	} else {
 		return err
d5151ca8
 	}
 
f5f7fee2
 	dataset := zfs.Dataset{Name: d.zfsPath(id)}
11e9167a
 	if err := dataset.Destroy(zfs.DestroyRecursiveClones); err != nil {
d5151ca8
 		return err
 	}
11e9167a
 
 	// retry
e9183404
 	return d.create(id, parent, storageOpt)
d5151ca8
 }
 
e9183404
 func (d *Driver) create(id, parent string, storageOpt map[string]string) error {
f5f7fee2
 	name := d.zfsPath(id)
e9183404
 	quota, err := parseStorageOpt(storageOpt)
373654f4
 	if err != nil {
 		return err
 	}
11e9167a
 	if parent == "" {
 		mountoptions := map[string]string{"mountpoint": "legacy"}
2cb23527
 		fs, err := zfs.CreateFilesystem(name, mountoptions)
 		if err == nil {
e9183404
 			err = setQuota(name, quota)
 			if err == nil {
 				d.Lock()
 				d.filesystemsCache[fs.Name] = true
 				d.Unlock()
 			}
2cb23527
 		}
d5151ca8
 		return err
 	}
e9183404
 	err = d.cloneFilesystem(name, d.zfsPath(parent))
 	if err == nil {
 		err = setQuota(name, quota)
 	}
 	return err
 }
 
 func parseStorageOpt(storageOpt map[string]string) (string, error) {
 	// Read size to change the disk quota per container
 	for k, v := range storageOpt {
 		key := strings.ToLower(k)
 		switch key {
 		case "size":
 			return v, nil
 		default:
 			return "0", fmt.Errorf("Unknown option %s", key)
2cb23527
 		}
e9183404
 	}
 	return "0", nil
 }
 
 func setQuota(name string, quota string) error {
 	if quota == "0" {
 		return nil
 	}
 	fs, err := zfs.GetDataset(name)
 	if err != nil {
d5151ca8
 		return err
 	}
e9183404
 	return fs.SetProperty("quota", quota)
11e9167a
 }
d5151ca8
 
e27c904b
 // Remove deletes the dataset, filesystem and the cache for the given id.
11e9167a
 func (d *Driver) Remove(id string) error {
f5f7fee2
 	name := d.zfsPath(id)
2cb23527
 	dataset := zfs.Dataset{Name: name}
 	err := dataset.Destroy(zfs.DestroyRecursive)
 	if err == nil {
 		d.Lock()
 		delete(d.filesystemsCache, name)
 		d.Unlock()
 	}
 	return err
d5151ca8
 }
 
e27c904b
 // Get returns the mountpoint for the given id after creating the target directories if necessary.
d5151ca8
 func (d *Driver) Get(id, mountLabel string) (string, error) {
f5f7fee2
 	mountpoint := d.mountPath(id)
009ee16b
 	if count := d.ctr.Increment(mountpoint); count > 1 {
7342060b
 		return mountpoint, nil
 	}
 
f5f7fee2
 	filesystem := d.zfsPath(id)
19c31a70
 	options := label.FormatMountLabel("", mountLabel)
2bf73c4b
 	logrus.Debugf(`[zfs] mount("%s", "%s", "%s")`, filesystem, mountpoint, options)
11e9167a
 
442b4562
 	rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
 	if err != nil {
009ee16b
 		d.ctr.Decrement(mountpoint)
442b4562
 		return "", err
 	}
11e9167a
 	// Create the target directories if they don't exist
442b4562
 	if err := idtools.MkdirAllAs(mountpoint, 0755, rootUID, rootGID); err != nil {
009ee16b
 		d.ctr.Decrement(mountpoint)
d5151ca8
 		return "", err
 	}
11e9167a
 
aef0995b
 	if err := mount.Mount(filesystem, mountpoint, "zfs", options); err != nil {
009ee16b
 		d.ctr.Decrement(mountpoint)
11e9167a
 		return "", fmt.Errorf("error creating zfs mount of %s to %s: %v", filesystem, mountpoint, err)
 	}
7342060b
 
aef0995b
 	// this could be our first mount after creation of the filesystem, and the root dir may still have root
 	// permissions instead of the remapped root uid:gid (if user namespaces are enabled):
 	if err := os.Chown(mountpoint, rootUID, rootGID); err != nil {
7342060b
 		mount.Unmount(mountpoint)
009ee16b
 		d.ctr.Decrement(mountpoint)
aef0995b
 		return "", fmt.Errorf("error modifying zfs mountpoint (%s) directory ownership: %v", mountpoint, err)
 	}
11e9167a
 
 	return mountpoint, nil
d5151ca8
 }
 
e27c904b
 // Put removes the existing mountpoint for the given id if it exists.
d5151ca8
 func (d *Driver) Put(id string) error {
009ee16b
 	mountpoint := d.mountPath(id)
 	if count := d.ctr.Decrement(mountpoint); count > 0 {
7342060b
 		return nil
 	}
65d79e3e
 	mounted, err := graphdriver.Mounted(graphdriver.FsMagicZfs, mountpoint)
 	if err != nil || !mounted {
 		return err
922986b7
 	}
 
65d79e3e
 	logrus.Debugf(`[zfs] unmount("%s")`, mountpoint)
922986b7
 
65d79e3e
 	if err := mount.Unmount(mountpoint); err != nil {
 		return fmt.Errorf("error unmounting to %s: %v", mountpoint, err)
11e9167a
 	}
d5151ca8
 	return nil
 }
 
e27c904b
 // Exists checks to see if the cache entry exists for the given id.
d5151ca8
 func (d *Driver) Exists(id string) bool {
65d79e3e
 	d.Lock()
 	defer d.Unlock()
f5f7fee2
 	return d.filesystemsCache[d.zfsPath(id)] == true
d5151ca8
 }