瀏覽代碼

Merge pull request #6224 from tiborvass/storage-options

Add --storage-opt daemon option and some devicemapper option (with fixes)
Tibor Vass 11 年之前
父節點
當前提交
9329c0d2e0

+ 2 - 2
daemon/daemon.go

@@ -780,7 +780,7 @@ func NewDaemonFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*D
 	graphdriver.DefaultDriver = config.GraphDriver
 
 	// Load storage driver
-	driver, err := graphdriver.New(config.Root)
+	driver, err := graphdriver.New(config.Root, config.GraphOptions)
 	if err != nil {
 		return nil, err
 	}
@@ -809,7 +809,7 @@ func NewDaemonFromDirectory(config *daemonconfig.Config, eng *engine.Engine) (*D
 
 	// We don't want to use a complex driver like aufs or devmapper
 	// for volumes, just a plain filesystem
-	volumesDriver, err := graphdriver.GetDriver("vfs", config.Root)
+	volumesDriver, err := graphdriver.GetDriver("vfs", config.Root, config.GraphOptions)
 	if err != nil {
 		return nil, err
 	}

+ 1 - 1
daemon/graphdriver/aufs/aufs.go

@@ -57,7 +57,7 @@ type Driver struct {
 
 // New returns a new AUFS driver.
 // An error is returned if AUFS is not supported.
-func Init(root string) (graphdriver.Driver, error) {
+func Init(root string, options []string) (graphdriver.Driver, error) {
 	// Try to load the aufs kernel module
 	if err := supportsAufs(); err != nil {
 		return nil, graphdriver.ErrNotSupported

+ 1 - 1
daemon/graphdriver/aufs/aufs_test.go

@@ -17,7 +17,7 @@ var (
 )
 
 func testInit(dir string, t *testing.T) graphdriver.Driver {
-	d, err := Init(dir)
+	d, err := Init(dir, nil)
 	if err != nil {
 		if err == graphdriver.ErrNotSupported {
 			t.Skip(err)

+ 1 - 1
daemon/graphdriver/btrfs/btrfs.go

@@ -22,7 +22,7 @@ func init() {
 	graphdriver.Register("btrfs", Init)
 }
 
-func Init(home string) (graphdriver.Driver, error) {
+func Init(home string, options []string) (graphdriver.Driver, error) {
 	rootdir := path.Dir(home)
 
 	var buf syscall.Statfs_t

+ 143 - 0
daemon/graphdriver/devmapper/README.md

@@ -0,0 +1,143 @@
+## devicemapper - a storage backend based on Device Mapper
+
+### Theory of operation
+
+The device mapper graphdriver uses the device mapper thin provisioning
+module (dm-thinp) to implement CoW snapshots. For each devicemapper
+graph location (typically `/var/lib/docker/devicemapper`, $graph below)
+a thin pool is created based on two block devices, one for data and
+one for metadata.  By default these block devices are created
+automatically by using loopback mounts of automatically creates sparse
+files.
+
+The default loopback files used are `$graph/devicemapper/data` and
+`$graph/devicemapper/metadata`. Additional metadata required to map
+from docker entities to the corresponding devicemapper volumes is
+stored in the `$graph/devicemapper/json` file (encoded as Json).
+
+In order to support multiple devicemapper graphs on a system the thin
+pool will be named something like: `docker-0:33-19478248-pool`, where
+the `0:30` part is the minor/major device nr and `19478248` is the
+inode number of the $graph directory.
+
+On the thin pool docker automatically creates a base thin device,
+called something like `docker-0:33-19478248-base` of a fixed
+size. This is automatically formated on creation and contains just an
+empty filesystem. This device is the base of all docker images and
+containers. All base images are snapshots of this device and those
+images are then in turn used as snapshots for other images and
+eventually containers.
+
+### options
+
+The devicemapper backend supports some options that you can specify
+when starting the docker daemon using the --storage-opt flags.
+This uses the `dm` prefix and would be used somthing like `docker -d --storage-opt dm.foo=bar`.
+
+Here is the list of supported options:
+
+ *  `dm.basesize`
+
+    Specifies the size to use when creating the base device, which
+    limits the size of images and containers. The default value is
+    10G. Note, thin devices are inherently "sparse", so a 10G device
+    which is mostly empty doesn't use 10 GB of space on the
+    pool. However, the filesystem will use more space for the empty
+    case the larger the device is.
+
+    Example use:
+
+    ``docker -d --storage-opt dm.basesize=20G``
+
+ *  `dm.loopdatasize`
+
+    Specifies the size to use when creating the loopback file for the
+    "data" device which is used for the thin pool. The default size is
+    100G. Note that the file is sparse, so it will not initially take
+    up this much space.
+
+    Example use:
+
+    ``docker -d --storage-opt dm.loopdatasize=200G``
+
+ *  `dm.loopmetadatasize`
+
+    Specifies the size to use when creating the loopback file for the
+    "metadadata" device which is used for the thin pool. The default size is
+    2G. Note that the file is sparse, so it will not initially take
+    up this much space.
+
+    Example use:
+
+    ``docker -d --storage-opt dm.loopmetadatasize=4G``
+
+ *  `dm.fs`
+
+    Specifies the filesystem type to use for the base device. The supported
+    options are "ext4" and "xfs". The default is "ext4"
+
+    Example use:
+
+    ``docker -d --storage-opt dm.fs=xfs``
+
+ *  `dm.mkfsarg`
+
+    Specifies extra mkfs arguments to be used when creating the base device.
+
+    Example use:
+
+    ``docker -d --storage-opt "dm.mkfsarg=-O ^has_journal"``
+
+ *  `dm.mountopt`
+
+    Specifies extra mount options used when mounting the thin devices.
+
+    Example use:
+
+    ``docker -d --storage-opt dm.mountopt=nodiscard``
+
+ *  `dm.datadev`
+
+    Specifies a custom blockdevice to use for data for the thin pool.
+
+    If using a block device for device mapper storage, ideally both
+    datadev and metadatadev should be specified to completely avoid
+    using the loopback device.
+
+    Example use:
+
+    ``docker -d --storage-opt dm.datadev=/dev/sdb1 --storage-opt dm.metadatadev=/dev/sdc1``
+
+ *  `dm.metadatadev`
+
+    Specifies a custom blockdevice to use for metadata for the thin
+    pool.
+
+    For best performance the metadata should be on a different spindle
+    than the data, or even better on an SSD.
+
+    If setting up a new metadata pool it is required to be valid. This
+    can be achieved by zeroing the first 4k to indicate empty
+    metadata, like this:
+
+    ``dd if=/dev/zero of=$metadata_dev bs=4096 count=1```
+
+    Example use:
+
+    ``docker -d --storage-opt dm.datadev=/dev/sdb1 --storage-opt dm.metadatadev=/dev/sdc1``
+
+ *  `dm.blkdiscard`
+
+    Enables or disables the use of blkdiscard when removing
+    devicemapper devices. This is enabled by default (only) if using
+    loopback devices and is required to res-parsify the loopback file
+    on image/container removal.
+
+    Disabling this on loopback can lead to *much* faster container
+    removal times, but will make the space used in /var/lib/docker
+    directory not be returned to the system for other use when
+    containers are removed.
+
+    Example use:
+
+    ``docker -d --storage-opt dm.blkdiscard=false``

+ 186 - 62
daemon/graphdriver/devmapper/deviceset.go

@@ -13,11 +13,14 @@ import (
 	"path"
 	"path/filepath"
 	"strconv"
+	"strings"
 	"sync"
 	"syscall"
 	"time"
 
+	"github.com/dotcloud/docker/daemon/graphdriver"
 	"github.com/dotcloud/docker/pkg/label"
+	"github.com/dotcloud/docker/pkg/units"
 	"github.com/dotcloud/docker/utils"
 )
 
@@ -64,6 +67,17 @@ type DeviceSet struct {
 	TransactionId    uint64
 	NewTransactionId uint64
 	nextDeviceId     int
+
+	// Options
+	dataLoopbackSize     int64
+	metaDataLoopbackSize int64
+	baseFsSize           uint64
+	filesystem           string
+	mountOptions         string
+	mkfsArgs             []string
+	dataDevice           string
+	metadataDevice       string
+	doBlkDiscard         bool
 }
 
 type DiskUsage struct {
@@ -273,26 +287,39 @@ func (devices *DeviceSet) activateDeviceIfNeeded(info *DevInfo) error {
 func (devices *DeviceSet) createFilesystem(info *DevInfo) error {
 	devname := info.DevName()
 
-	err := exec.Command("mkfs.ext4", "-E", "nodiscard,lazy_itable_init=0,lazy_journal_init=0", devname).Run()
-	if err != nil {
-		err = exec.Command("mkfs.ext4", "-E", "nodiscard,lazy_itable_init=0", devname).Run()
+	args := []string{}
+	for _, arg := range devices.mkfsArgs {
+		args = append(args, arg)
+	}
+
+	args = append(args, devname)
+
+	var err error
+	switch devices.filesystem {
+	case "xfs":
+		err = exec.Command("mkfs.xfs", args...).Run()
+	case "ext4":
+		err = exec.Command("mkfs.ext4", append([]string{"-E", "nodiscard,lazy_itable_init=0,lazy_journal_init=0"}, args...)...).Run()
+		if err != nil {
+			err = exec.Command("mkfs.ext4", append([]string{"-E", "nodiscard,lazy_itable_init=0"}, args...)...).Run()
+		}
+	default:
+		err = fmt.Errorf("Unsupported filesystem type %s", devices.filesystem)
 	}
 	if err != nil {
-		utils.Debugf("\n--->Err: %s\n", err)
 		return err
 	}
+
 	return nil
 }
 
 func (devices *DeviceSet) initMetaData() error {
 	_, _, _, params, err := getStatus(devices.getPoolName())
 	if err != nil {
-		utils.Debugf("\n--->Err: %s\n", err)
 		return err
 	}
 
 	if _, err := fmt.Sscanf(params, "%d", &devices.TransactionId); err != nil {
-		utils.Debugf("\n--->Err: %s\n", err)
 		return err
 	}
 	devices.NewTransactionId = devices.TransactionId
@@ -301,7 +328,6 @@ func (devices *DeviceSet) initMetaData() error {
 
 	jsonData, err := ioutil.ReadFile(devices.oldMetadataFile())
 	if err != nil && !os.IsNotExist(err) {
-		utils.Debugf("\n--->Err: %s\n", err)
 		return err
 	}
 
@@ -309,7 +335,6 @@ func (devices *DeviceSet) initMetaData() error {
 		m := MetaData{Devices: make(map[string]*DevInfo)}
 
 		if err := json.Unmarshal(jsonData, &m); err != nil {
-			utils.Debugf("\n--->Err: %s\n", err)
 			return err
 		}
 
@@ -359,7 +384,6 @@ func (devices *DeviceSet) setupBaseImage() error {
 	if oldInfo != nil && !oldInfo.Initialized {
 		utils.Debugf("Removing uninitialized base image")
 		if err := devices.deleteDevice(oldInfo); err != nil {
-			utils.Debugf("\n--->Err: %s\n", err)
 			return err
 		}
 	}
@@ -370,37 +394,32 @@ func (devices *DeviceSet) setupBaseImage() error {
 
 	// Create initial device
 	if err := createDevice(devices.getPoolDevName(), &id); err != nil {
-		utils.Debugf("\n--->Err: %s\n", err)
 		return err
 	}
 
 	// Ids are 24bit, so wrap around
 	devices.nextDeviceId = (id + 1) & 0xffffff
 
-	utils.Debugf("Registering base device (id %v) with FS size %v", id, DefaultBaseFsSize)
-	info, err := devices.registerDevice(id, "", DefaultBaseFsSize)
+	utils.Debugf("Registering base device (id %v) with FS size %v", id, devices.baseFsSize)
+	info, err := devices.registerDevice(id, "", devices.baseFsSize)
 	if err != nil {
 		_ = deleteDevice(devices.getPoolDevName(), id)
-		utils.Debugf("\n--->Err: %s\n", err)
 		return err
 	}
 
 	utils.Debugf("Creating filesystem on base device-manager snapshot")
 
 	if err = devices.activateDeviceIfNeeded(info); err != nil {
-		utils.Debugf("\n--->Err: %s\n", err)
 		return err
 	}
 
 	if err := devices.createFilesystem(info); err != nil {
-		utils.Debugf("\n--->Err: %s\n", err)
 		return err
 	}
 
 	info.Initialized = true
 	if err = devices.saveMetadata(info); err != nil {
 		info.Initialized = false
-		utils.Debugf("\n--->Err: %s\n", err)
 		return err
 	}
 
@@ -506,6 +525,12 @@ func (devices *DeviceSet) ResizePool(size int64) error {
 func (devices *DeviceSet) initDevmapper(doInit bool) error {
 	logInit(devices)
 
+	_, err := getDriverVersion()
+	if err != nil {
+		// Can't even get driver version, assume not supported
+		return graphdriver.ErrNotSupported
+	}
+
 	if err := os.MkdirAll(devices.metadataDir(), 0700); err != nil && !os.IsExist(err) {
 		return err
 	}
@@ -548,45 +573,74 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
 	if info.Exists == 0 {
 		utils.Debugf("Pool doesn't exist. Creating it.")
 
-		hasData := devices.hasImage("data")
-		hasMetadata := devices.hasImage("metadata")
+		var (
+			dataFile     *os.File
+			metadataFile *os.File
+		)
 
-		if !doInit && !hasData {
-			return errors.New("Loopback data file not found")
-		}
+		if devices.dataDevice == "" {
+			// Make sure the sparse images exist in <root>/devicemapper/data
 
-		if !doInit && !hasMetadata {
-			return errors.New("Loopback metadata file not found")
-		}
+			hasData := devices.hasImage("data")
 
-		createdLoopback = !hasData || !hasMetadata
-		data, err := devices.ensureImage("data", DefaultDataLoopbackSize)
-		if err != nil {
-			utils.Debugf("Error device ensureImage (data): %s\n", err)
-			return err
-		}
-		metadata, err := devices.ensureImage("metadata", DefaultMetaDataLoopbackSize)
-		if err != nil {
-			utils.Debugf("Error device ensureImage (metadata): %s\n", err)
-			return err
-		}
+			if !doInit && !hasData {
+				return errors.New("Loopback data file not found")
+			}
 
-		dataFile, err := attachLoopDevice(data)
-		if err != nil {
-			utils.Debugf("\n--->Err: %s\n", err)
-			return err
+			if !hasData {
+				createdLoopback = true
+			}
+
+			data, err := devices.ensureImage("data", devices.dataLoopbackSize)
+			if err != nil {
+				utils.Debugf("Error device ensureImage (data): %s\n", err)
+				return err
+			}
+
+			dataFile, err = attachLoopDevice(data)
+			if err != nil {
+				return err
+			}
+		} else {
+			dataFile, err = os.OpenFile(devices.dataDevice, os.O_RDWR, 0600)
+			if err != nil {
+				return err
+			}
 		}
 		defer dataFile.Close()
 
-		metadataFile, err := attachLoopDevice(metadata)
-		if err != nil {
-			utils.Debugf("\n--->Err: %s\n", err)
-			return err
+		if devices.metadataDevice == "" {
+			// Make sure the sparse images exist in <root>/devicemapper/metadata
+
+			hasMetadata := devices.hasImage("metadata")
+
+			if !doInit && !hasMetadata {
+				return errors.New("Loopback metadata file not found")
+			}
+
+			if !hasMetadata {
+				createdLoopback = true
+			}
+
+			metadata, err := devices.ensureImage("metadata", devices.metaDataLoopbackSize)
+			if err != nil {
+				utils.Debugf("Error device ensureImage (metadata): %s\n", err)
+				return err
+			}
+
+			metadataFile, err = attachLoopDevice(metadata)
+			if err != nil {
+				return err
+			}
+		} else {
+			metadataFile, err = os.OpenFile(devices.metadataDevice, os.O_RDWR, 0600)
+			if err != nil {
+				return err
+			}
 		}
 		defer metadataFile.Close()
 
 		if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil {
-			utils.Debugf("\n--->Err: %s\n", err)
 			return err
 		}
 	}
@@ -595,7 +649,6 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error {
 	// load the transaction id and migrate old metadata
 	if !createdLoopback {
 		if err = devices.initMetaData(); err != nil {
-			utils.Debugf("\n--->Err: %s\n", err)
 			return err
 		}
 	}
@@ -646,12 +699,14 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error {
 }
 
 func (devices *DeviceSet) deleteDevice(info *DevInfo) error {
-	// This is a workaround for the kernel not discarding block so
-	// on the thin pool when we remove a thinp device, so we do it
-	// manually
-	if err := devices.activateDeviceIfNeeded(info); err == nil {
-		if err := BlockDeviceDiscard(info.DevName()); err != nil {
-			utils.Debugf("Error discarding block on device: %s (ignoring)\n", err)
+	if devices.doBlkDiscard {
+		// This is a workaround for the kernel not discarding block so
+		// on the thin pool when we remove a thinp device, so we do it
+		// manually
+		if err := devices.activateDeviceIfNeeded(info); err == nil {
+			if err := BlockDeviceDiscard(info.DevName()); err != nil {
+				utils.Debugf("Error discarding block on device: %s (ignoring)\n", err)
+			}
 		}
 	}
 
@@ -705,7 +760,6 @@ func (devices *DeviceSet) deactivatePool() error {
 	devname := devices.getPoolDevName()
 	devinfo, err := getInfo(devname)
 	if err != nil {
-		utils.Debugf("\n--->Err: %s\n", err)
 		return err
 	}
 	if devinfo.Exists != 0 {
@@ -727,12 +781,10 @@ func (devices *DeviceSet) deactivateDevice(info *DevInfo) error {
 
 	devinfo, err := getInfo(info.Name())
 	if err != nil {
-		utils.Debugf("\n--->Err: %s\n", err)
 		return err
 	}
 	if devinfo.Exists != 0 {
 		if err := devices.removeDeviceAndWait(info.Name()); err != nil {
-			utils.Debugf("\n--->Err: %s\n", err)
 			return err
 		}
 	}
@@ -907,11 +959,24 @@ func (devices *DeviceSet) MountDevice(hash, path, mountLabel string) error {
 
 	var flags uintptr = syscall.MS_MGC_VAL
 
-	mountOptions := label.FormatMountLabel("discard", mountLabel)
-	err = syscall.Mount(info.DevName(), path, "ext4", flags, mountOptions)
+	fstype, err := ProbeFsType(info.DevName())
+	if err != nil {
+		return err
+	}
+
+	options := ""
+
+	if fstype == "xfs" {
+		// XFS needs nouuid or it can't mount filesystems with the same fs
+		options = joinMountOptions(options, "nouuid")
+	}
+
+	options = joinMountOptions(options, devices.mountOptions)
+	options = joinMountOptions(options, label.FormatMountLabel("", mountLabel))
+
+	err = syscall.Mount(info.DevName(), path, fstype, flags, joinMountOptions("discard", options))
 	if err != nil && err == syscall.EINVAL {
-		mountOptions = label.FormatMountLabel("", mountLabel)
-		err = syscall.Mount(info.DevName(), path, "ext4", flags, mountOptions)
+		err = syscall.Mount(info.DevName(), path, fstype, flags, options)
 	}
 	if err != nil {
 		return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err)
@@ -949,7 +1014,6 @@ func (devices *DeviceSet) UnmountDevice(hash string) error {
 
 	utils.Debugf("[devmapper] Unmount(%s)", info.mountPath)
 	if err := syscall.Unmount(info.mountPath, 0); err != nil {
-		utils.Debugf("\n--->Err: %s\n", err)
 		return err
 	}
 	utils.Debugf("[devmapper] Unmount done")
@@ -1084,12 +1148,72 @@ func (devices *DeviceSet) Status() *Status {
 	return status
 }
 
-func NewDeviceSet(root string, doInit bool) (*DeviceSet, error) {
+func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error) {
 	SetDevDir("/dev")
 
 	devices := &DeviceSet{
-		root:     root,
-		MetaData: MetaData{Devices: make(map[string]*DevInfo)},
+		root:                 root,
+		MetaData:             MetaData{Devices: make(map[string]*DevInfo)},
+		dataLoopbackSize:     DefaultDataLoopbackSize,
+		metaDataLoopbackSize: DefaultMetaDataLoopbackSize,
+		baseFsSize:           DefaultBaseFsSize,
+		filesystem:           "ext4",
+		doBlkDiscard:         true,
+	}
+
+	foundBlkDiscard := false
+	for _, option := range options {
+		key, val, err := utils.ParseKeyValueOpt(option)
+		if err != nil {
+			return nil, err
+		}
+		key = strings.ToLower(key)
+		switch key {
+		case "dm.basesize":
+			size, err := units.FromHumanSize(val)
+			if err != nil {
+				return nil, err
+			}
+			devices.baseFsSize = uint64(size)
+		case "dm.loopdatasize":
+			size, err := units.FromHumanSize(val)
+			if err != nil {
+				return nil, err
+			}
+			devices.dataLoopbackSize = size
+		case "dm.loopmetadatasize":
+			size, err := units.FromHumanSize(val)
+			if err != nil {
+				return nil, err
+			}
+			devices.metaDataLoopbackSize = size
+		case "dm.fs":
+			if val != "ext4" && val != "xfs" {
+				return nil, fmt.Errorf("Unsupported filesystem %s\n", val)
+			}
+			devices.filesystem = val
+		case "dm.mkfsarg":
+			devices.mkfsArgs = append(devices.mkfsArgs, val)
+		case "dm.mountopt":
+			devices.mountOptions = joinMountOptions(devices.mountOptions, val)
+		case "dm.metadatadev":
+			devices.metadataDevice = val
+		case "dm.datadev":
+			devices.dataDevice = val
+		case "dm.blkdiscard":
+			foundBlkDiscard = true
+			devices.doBlkDiscard, err = strconv.ParseBool(val)
+			if err != nil {
+				return nil, err
+			}
+		default:
+			return nil, fmt.Errorf("Unknown option %s\n", key)
+		}
+	}
+
+	// By default, don't do blk discard hack on raw devices, its rarely useful and is expensive
+	if !foundBlkDiscard && devices.dataDevice != "" {
+		devices.doBlkDiscard = false
 	}
 
 	if err := devices.initDevmapper(doInit); err != nil {

+ 20 - 0
daemon/graphdriver/devmapper/devmapper.go

@@ -52,6 +52,7 @@ var (
 	ErrTaskAddTarget          = errors.New("dm_task_add_target failed")
 	ErrTaskSetSector          = errors.New("dm_task_set_sector failed")
 	ErrTaskGetInfo            = errors.New("dm_task_get_info failed")
+	ErrTaskGetDriverVersion   = errors.New("dm_task_get_driver_version failed")
 	ErrTaskSetCookie          = errors.New("dm_task_set_cookie failed")
 	ErrNilCookie              = errors.New("cookie ptr can't be nil")
 	ErrAttachLoopbackDevice   = errors.New("loopback mounting failed")
@@ -178,6 +179,14 @@ func (t *Task) GetInfo() (*Info, error) {
 	return info, nil
 }
 
+func (t *Task) GetDriverVersion() (string, error) {
+	res := DmTaskGetDriverVersion(t.unmanaged)
+	if res == "" {
+		return "", ErrTaskGetDriverVersion
+	}
+	return res, nil
+}
+
 func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64,
 	length uint64, targetType string, params string) {
 
@@ -394,6 +403,17 @@ func getInfo(name string) (*Info, error) {
 	return task.GetInfo()
 }
 
+func getDriverVersion() (string, error) {
+	task := TaskCreate(DeviceVersion)
+	if task == nil {
+		return "", fmt.Errorf("Can't create DeviceVersion task")
+	}
+	if err := task.Run(); err != nil {
+		return "", err
+	}
+	return task.GetDriverVersion()
+}
+
 func getStatus(name string) (uint64, uint64, string, string, error) {
 	task, err := createTask(DeviceStatus, name)
 	if task == nil {

+ 28 - 17
daemon/graphdriver/devmapper/devmapper_wrapper.go

@@ -85,23 +85,24 @@ const (
 )
 
 var (
-	DmGetLibraryVersion = dmGetLibraryVersionFct
-	DmGetNextTarget     = dmGetNextTargetFct
-	DmLogInitVerbose    = dmLogInitVerboseFct
-	DmSetDevDir         = dmSetDevDirFct
-	DmTaskAddTarget     = dmTaskAddTargetFct
-	DmTaskCreate        = dmTaskCreateFct
-	DmTaskDestroy       = dmTaskDestroyFct
-	DmTaskGetInfo       = dmTaskGetInfoFct
-	DmTaskRun           = dmTaskRunFct
-	DmTaskSetAddNode    = dmTaskSetAddNodeFct
-	DmTaskSetCookie     = dmTaskSetCookieFct
-	DmTaskSetMessage    = dmTaskSetMessageFct
-	DmTaskSetName       = dmTaskSetNameFct
-	DmTaskSetRo         = dmTaskSetRoFct
-	DmTaskSetSector     = dmTaskSetSectorFct
-	DmUdevWait          = dmUdevWaitFct
-	LogWithErrnoInit    = logWithErrnoInitFct
+	DmGetLibraryVersion    = dmGetLibraryVersionFct
+	DmGetNextTarget        = dmGetNextTargetFct
+	DmLogInitVerbose       = dmLogInitVerboseFct
+	DmSetDevDir            = dmSetDevDirFct
+	DmTaskAddTarget        = dmTaskAddTargetFct
+	DmTaskCreate           = dmTaskCreateFct
+	DmTaskDestroy          = dmTaskDestroyFct
+	DmTaskGetInfo          = dmTaskGetInfoFct
+	DmTaskGetDriverVersion = dmTaskGetDriverVersionFct
+	DmTaskRun              = dmTaskRunFct
+	DmTaskSetAddNode       = dmTaskSetAddNodeFct
+	DmTaskSetCookie        = dmTaskSetCookieFct
+	DmTaskSetMessage       = dmTaskSetMessageFct
+	DmTaskSetName          = dmTaskSetNameFct
+	DmTaskSetRo            = dmTaskSetRoFct
+	DmTaskSetSector        = dmTaskSetSectorFct
+	DmUdevWait             = dmUdevWaitFct
+	LogWithErrnoInit       = logWithErrnoInitFct
 )
 
 func free(p *C.char) {
@@ -184,6 +185,16 @@ func dmTaskGetInfoFct(task *CDmTask, info *Info) int {
 	return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo))
 }
 
+func dmTaskGetDriverVersionFct(task *CDmTask) string {
+	buffer := C.malloc(128)
+	defer C.free(buffer)
+	res := C.dm_task_get_driver_version((*C.struct_dm_task)(task), (*C.char)(buffer), 128)
+	if res == 0 {
+		return ""
+	}
+	return C.GoString((*C.char)(buffer))
+}
+
 func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
 	var (
 		Cstart, Clength      C.uint64_t

+ 2 - 2
daemon/graphdriver/devmapper/driver.go

@@ -26,8 +26,8 @@ type Driver struct {
 	home string
 }
 
-func Init(home string) (graphdriver.Driver, error) {
-	deviceSet, err := NewDeviceSet(home, true)
+func Init(home string, options []string) (graphdriver.Driver, error) {
+	deviceSet, err := NewDeviceSet(home, true, options)
 	if err != nil {
 		return nil, err
 	}

+ 57 - 0
daemon/graphdriver/devmapper/mount.go

@@ -3,6 +3,8 @@
 package devmapper
 
 import (
+	"bytes"
+	"fmt"
 	"os"
 	"path/filepath"
 	"syscall"
@@ -27,3 +29,58 @@ func Mounted(mountpoint string) (bool, error) {
 	parentSt := parent.Sys().(*syscall.Stat_t)
 	return mntpointSt.Dev != parentSt.Dev, nil
 }
+
+type probeData struct {
+	fsName string
+	magic  string
+	offset uint64
+}
+
+func ProbeFsType(device string) (string, error) {
+	probes := []probeData{
+		{"btrfs", "_BHRfS_M", 0x10040},
+		{"ext4", "\123\357", 0x438},
+		{"xfs", "XFSB", 0},
+	}
+
+	maxLen := uint64(0)
+	for _, p := range probes {
+		l := p.offset + uint64(len(p.magic))
+		if l > maxLen {
+			maxLen = l
+		}
+	}
+
+	file, err := os.Open(device)
+	if err != nil {
+		return "", err
+	}
+
+	buffer := make([]byte, maxLen)
+	l, err := file.Read(buffer)
+	if err != nil {
+		return "", err
+	}
+	file.Close()
+	if uint64(l) != maxLen {
+		return "", fmt.Errorf("unable to detect filesystem type of %s, short read", device)
+	}
+
+	for _, p := range probes {
+		if bytes.Equal([]byte(p.magic), buffer[p.offset:p.offset+uint64(len(p.magic))]) {
+			return p.fsName, nil
+		}
+	}
+
+	return "", fmt.Errorf("Unknown filesystem type on %s", device)
+}
+
+func joinMountOptions(a, b string) string {
+	if a == "" {
+		return b
+	}
+	if b == "" {
+		return a
+	}
+	return a + "," + b
+}

+ 7 - 7
daemon/graphdriver/driver.go

@@ -15,7 +15,7 @@ const (
 	FsMagicAufs  = FsMagic(0x61756673)
 )
 
-type InitFunc func(root string) (Driver, error)
+type InitFunc func(root string, options []string) (Driver, error)
 
 type Driver interface {
 	String() string
@@ -69,23 +69,23 @@ func Register(name string, initFunc InitFunc) error {
 	return nil
 }
 
-func GetDriver(name, home string) (Driver, error) {
+func GetDriver(name, home string, options []string) (Driver, error) {
 	if initFunc, exists := drivers[name]; exists {
-		return initFunc(path.Join(home, name))
+		return initFunc(path.Join(home, name), options)
 	}
 	return nil, ErrNotSupported
 }
 
-func New(root string) (driver Driver, err error) {
+func New(root string, options []string) (driver Driver, err error) {
 	for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} {
 		if name != "" {
-			return GetDriver(name, root)
+			return GetDriver(name, root, options)
 		}
 	}
 
 	// Check for priority drivers first
 	for _, name := range priority {
-		driver, err = GetDriver(name, root)
+		driver, err = GetDriver(name, root, options)
 		if err != nil {
 			if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS {
 				continue
@@ -97,7 +97,7 @@ func New(root string) (driver Driver, err error) {
 
 	// Check all registered drivers if no priority driver is found
 	for _, initFunc := range drivers {
-		if driver, err = initFunc(root); err != nil {
+		if driver, err = initFunc(root, options); err != nil {
 			if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS {
 				continue
 			}

+ 1 - 1
daemon/graphdriver/graphtest/graphtest.go

@@ -29,7 +29,7 @@ func newDriver(t *testing.T, name string) *Driver {
 		t.Fatal(err)
 	}
 
-	d, err := graphdriver.GetDriver(name, root)
+	d, err := graphdriver.GetDriver(name, root, nil)
 	if err != nil {
 		if err == graphdriver.ErrNotSupported || err == graphdriver.ErrPrerequisites {
 			t.Skip("Driver %s not supported", name)

+ 1 - 1
daemon/graphdriver/vfs/driver.go

@@ -12,7 +12,7 @@ func init() {
 	graphdriver.Register("vfs", Init)
 }
 
-func Init(home string) (graphdriver.Driver, error) {
+func Init(home string, options []string) (graphdriver.Driver, error) {
 	d := &Driver{
 		home: home,
 	}

+ 5 - 0
daemonconfig/config.go

@@ -25,6 +25,7 @@ type Config struct {
 	BridgeIP                    string
 	InterContainerCommunication bool
 	GraphDriver                 string
+	GraphOptions                []string
 	ExecDriver                  string
 	Mtu                         int
 	DisableNetwork              bool
@@ -49,6 +50,10 @@ func ConfigFromJob(job *engine.Job) *Config {
 		ExecDriver:                  job.Getenv("ExecDriver"),
 		EnableSelinuxSupport:        job.GetenvBool("EnableSelinuxSupport"),
 	}
+	if graphOpts := job.GetenvList("GraphOptions"); graphOpts != nil {
+		config.GraphOptions = graphOpts
+	}
+
 	if dns := job.GetenvList("Dns"); dns != nil {
 		config.Dns = dns
 	}

+ 3 - 0
docker/docker.go

@@ -41,6 +41,7 @@ func main() {
 	var (
 		flVersion            = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit")
 		flDaemon             = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode")
+		flGraphOpts          opts.ListOpts
 		flDebug              = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode")
 		flAutoRestart        = flag.Bool([]string{"r", "-restart"}, true, "Restart previously running containers")
 		bridgeName           = flag.String([]string{"b", "-bridge"}, "", "Attach containers to a pre-existing network bridge\nuse 'none' to disable container networking")
@@ -69,6 +70,7 @@ func main() {
 	flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers")
 	flag.Var(&flDnsSearch, []string{"-dns-search"}, "Force Docker to use specific DNS search domains")
 	flag.Var(&flHosts, []string{"H", "-host"}, "The socket(s) to bind to in daemon mode\nspecified using one or more tcp://host:port, unix:///path/to/socket, fd://* or fd://socketfd.")
+	flag.Var(&flGraphOpts, []string{"-storage-opt"}, "Set storage driver options")
 
 	flag.Parse()
 
@@ -156,6 +158,7 @@ func main() {
 			job.Setenv("DefaultIp", *flDefaultIp)
 			job.SetenvBool("InterContainerCommunication", *flInterContainerComm)
 			job.Setenv("GraphDriver", *flGraphDriver)
+			job.SetenvList("GraphOptions", flGraphOpts.GetAll())
 			job.Setenv("ExecDriver", *flExecDriver)
 			job.SetenvInt("Mtu", *flMtu)
 			job.SetenvBool("EnableSelinuxSupport", *flSelinuxEnabled)

+ 1 - 0
docs/sources/reference/commandline/cli.md

@@ -73,6 +73,7 @@ expect an integer, and they can only be specified once.
       -p, --pidfile="/var/run/docker.pid"        Path to use for daemon PID file
       -r, --restart=true                         Restart previously running containers
       -s, --storage-driver=""                    Force the docker runtime to use a specific storage driver
+      --storage-opt=[]                           Set storage driver options
       --selinux-enabled=false                    Enable selinux support
       --tls=false                                Use TLS; implied by tls-verify flags
       --tlscacert="/home/sven/.docker/ca.pem"    Trust only remotes providing a certificate signed by the CA given here

+ 1 - 1
graph/tags_unit_test.go

@@ -36,7 +36,7 @@ func fakeTar() (io.Reader, error) {
 }
 
 func mkTestTagStore(root string, t *testing.T) *TagStore {
-	driver, err := graphdriver.New(root)
+	driver, err := graphdriver.New(root, nil)
 	if err != nil {
 		t.Fatal(err)
 	}

+ 1 - 1
integration/graph_test.go

@@ -293,7 +293,7 @@ func tempGraph(t *testing.T) (*graph.Graph, graphdriver.Driver) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	driver, err := graphdriver.New(tmp)
+	driver, err := graphdriver.New(tmp, nil)
 	if err != nil {
 		t.Fatal(err)
 	}

+ 36 - 0
pkg/units/size.go

@@ -21,6 +21,42 @@ func HumanSize(size int64) string {
 	return fmt.Sprintf("%.4g %s", sizef, units[i])
 }
 
+// FromHumanSize returns an integer from a human-readable specification of a size
+// using SI standard (eg. "44kB", "17MB")
+func FromHumanSize(size string) (int64, error) {
+	re, error := regexp.Compile("^(\\d+)([kKmMgGtTpP])?[bB]?$")
+	if error != nil {
+		return -1, fmt.Errorf("%s does not specify not a size", size)
+	}
+
+	matches := re.FindStringSubmatch(size)
+
+	if len(matches) != 3 {
+		return -1, fmt.Errorf("Invalid size: '%s'", size)
+	}
+
+	theSize, error := strconv.ParseInt(matches[1], 10, 0)
+	if error != nil {
+		return -1, error
+	}
+
+	unit := strings.ToLower(matches[2])
+
+	if unit == "k" {
+		theSize *= 1000
+	} else if unit == "m" {
+		theSize *= 1000 * 1000
+	} else if unit == "g" {
+		theSize *= 1000 * 1000 * 1000
+	} else if unit == "t" {
+		theSize *= 1000 * 1000 * 1000 * 1000
+	} else if unit == "p" {
+		theSize *= 1000 * 1000 * 1000 * 1000 * 1000
+	}
+
+	return theSize, nil
+}
+
 // Parses a human-readable string representing an amount of RAM
 // in bytes, kibibytes, mebibytes or gibibytes, and returns the
 // number of bytes, or -1 if the string is unparseable.

+ 35 - 0
pkg/units/size_test.go

@@ -20,6 +20,41 @@ func TestHumanSize(t *testing.T) {
 	}
 }
 
+func TestFromHumanSize(t *testing.T) {
+	assertFromHumanSize(t, "32", false, 32)
+	assertFromHumanSize(t, "32b", false, 32)
+	assertFromHumanSize(t, "32B", false, 32)
+	assertFromHumanSize(t, "32k", false, 32*1000)
+	assertFromHumanSize(t, "32K", false, 32*1000)
+	assertFromHumanSize(t, "32kb", false, 32*1000)
+	assertFromHumanSize(t, "32Kb", false, 32*1000)
+	assertFromHumanSize(t, "32Mb", false, 32*1000*1000)
+	assertFromHumanSize(t, "32Gb", false, 32*1000*1000*1000)
+	assertFromHumanSize(t, "32Tb", false, 32*1000*1000*1000*1000)
+	assertFromHumanSize(t, "8Pb", false, 8*1000*1000*1000*1000*1000)
+
+	assertFromHumanSize(t, "", true, -1)
+	assertFromHumanSize(t, "hello", true, -1)
+	assertFromHumanSize(t, "-32", true, -1)
+	assertFromHumanSize(t, " 32 ", true, -1)
+	assertFromHumanSize(t, "32 mb", true, -1)
+	assertFromHumanSize(t, "32m b", true, -1)
+	assertFromHumanSize(t, "32bm", true, -1)
+}
+
+func assertFromHumanSize(t *testing.T, size string, expectError bool, expectedBytes int64) {
+	actualBytes, err := FromHumanSize(size)
+	if (err != nil) && !expectError {
+		t.Errorf("Unexpected error parsing '%s': %s", size, err)
+	}
+	if (err == nil) && expectError {
+		t.Errorf("Expected to get an error parsing '%s', but got none (bytes=%d)", size, actualBytes)
+	}
+	if actualBytes != expectedBytes {
+		t.Errorf("Expected '%s' to parse as %d bytes, got %d", size, expectedBytes, actualBytes)
+	}
+}
+
 func TestRAMInBytes(t *testing.T) {
 	assertRAMInBytes(t, "32", false, 32)
 	assertRAMInBytes(t, "32b", false, 32)