summaryrefslogtreecommitdiff
path: root/runtime/graphdriver/devmapper
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/graphdriver/devmapper')
-rw-r--r--runtime/graphdriver/devmapper/attach_loopback.go126
-rw-r--r--runtime/graphdriver/devmapper/deviceset.go1122
-rw-r--r--runtime/graphdriver/devmapper/devmapper.go595
-rw-r--r--runtime/graphdriver/devmapper/devmapper_doc.go106
-rw-r--r--runtime/graphdriver/devmapper/devmapper_log.go15
-rw-r--r--runtime/graphdriver/devmapper/devmapper_test.go287
-rw-r--r--runtime/graphdriver/devmapper/devmapper_wrapper.go229
-rw-r--r--runtime/graphdriver/devmapper/driver.go142
-rw-r--r--runtime/graphdriver/devmapper/driver_test.go886
-rw-r--r--runtime/graphdriver/devmapper/ioctl.go71
-rw-r--r--runtime/graphdriver/devmapper/mount.go27
-rw-r--r--runtime/graphdriver/devmapper/sys.go57
12 files changed, 3663 insertions, 0 deletions
diff --git a/runtime/graphdriver/devmapper/attach_loopback.go b/runtime/graphdriver/devmapper/attach_loopback.go
new file mode 100644
index 0000000000..23339076e8
--- /dev/null
+++ b/runtime/graphdriver/devmapper/attach_loopback.go
@@ -0,0 +1,126 @@
+// +build linux,amd64
+
+package devmapper
+
+import (
+ "fmt"
+ "github.com/dotcloud/docker/utils"
+)
+
+func stringToLoopName(src string) [LoNameSize]uint8 {
+ var dst [LoNameSize]uint8
+ copy(dst[:], src[:])
+ return dst
+}
+
+func getNextFreeLoopbackIndex() (int, error) {
+ f, err := osOpenFile("/dev/loop-control", osORdOnly, 0644)
+ if err != nil {
+ return 0, err
+ }
+ defer f.Close()
+
+ index, err := ioctlLoopCtlGetFree(f.Fd())
+ if index < 0 {
+ index = 0
+ }
+ return index, err
+}
+
+func openNextAvailableLoopback(index int, sparseFile *osFile) (loopFile *osFile, err error) {
+ // Start looking for a free /dev/loop
+ for {
+ target := fmt.Sprintf("/dev/loop%d", index)
+ index++
+
+ fi, err := osStat(target)
+ if err != nil {
+ if osIsNotExist(err) {
+ utils.Errorf("There are no more loopback device available.")
+ }
+ return nil, ErrAttachLoopbackDevice
+ }
+
+ if fi.Mode()&osModeDevice != osModeDevice {
+ utils.Errorf("Loopback device %s is not a block device.", target)
+ continue
+ }
+
+ // OpenFile adds O_CLOEXEC
+ loopFile, err = osOpenFile(target, osORdWr, 0644)
+ if err != nil {
+ utils.Errorf("Error openning loopback device: %s", err)
+ return nil, ErrAttachLoopbackDevice
+ }
+
+ // Try to attach to the loop file
+ if err := ioctlLoopSetFd(loopFile.Fd(), sparseFile.Fd()); err != nil {
+ loopFile.Close()
+
+ // If the error is EBUSY, then try the next loopback
+ if err != sysEBusy {
+ utils.Errorf("Cannot set up loopback device %s: %s", target, err)
+ return nil, ErrAttachLoopbackDevice
+ }
+
+ // Otherwise, we keep going with the loop
+ continue
+ }
+ // In case of success, we finished. Break the loop.
+ break
+ }
+
+ // This can't happen, but let's be sure
+ if loopFile == nil {
+ utils.Errorf("Unreachable code reached! Error attaching %s to a loopback device.", sparseFile.Name())
+ return nil, ErrAttachLoopbackDevice
+ }
+
+ return loopFile, nil
+}
+
+// attachLoopDevice attaches the given sparse file to the next
+// available loopback device. It returns an opened *osFile.
+func attachLoopDevice(sparseName string) (loop *osFile, err error) {
+
+ // Try to retrieve the next available loopback device via syscall.
+ // If it fails, we discard error and start loopking for a
+ // loopback from index 0.
+ startIndex, err := getNextFreeLoopbackIndex()
+ if err != nil {
+ utils.Debugf("Error retrieving the next available loopback: %s", err)
+ }
+
+ // OpenFile adds O_CLOEXEC
+ sparseFile, err := osOpenFile(sparseName, osORdWr, 0644)
+ if err != nil {
+ utils.Errorf("Error openning sparse file %s: %s", sparseName, err)
+ return nil, ErrAttachLoopbackDevice
+ }
+ defer sparseFile.Close()
+
+ loopFile, err := openNextAvailableLoopback(startIndex, sparseFile)
+ if err != nil {
+ return nil, err
+ }
+
+ // Set the status of the loopback device
+ loopInfo := &LoopInfo64{
+ loFileName: stringToLoopName(loopFile.Name()),
+ loOffset: 0,
+ loFlags: LoFlagsAutoClear,
+ }
+
+ if err := ioctlLoopSetStatus64(loopFile.Fd(), loopInfo); err != nil {
+ utils.Errorf("Cannot set up loopback device info: %s", err)
+
+ // If the call failed, then free the loopback device
+ if err := ioctlLoopClrFd(loopFile.Fd()); err != nil {
+ utils.Errorf("Error while cleaning up the loopback device")
+ }
+ loopFile.Close()
+ return nil, ErrAttachLoopbackDevice
+ }
+
+ return loopFile, nil
+}
diff --git a/runtime/graphdriver/devmapper/deviceset.go b/runtime/graphdriver/devmapper/deviceset.go
new file mode 100644
index 0000000000..97d670a3d9
--- /dev/null
+++ b/runtime/graphdriver/devmapper/deviceset.go
@@ -0,0 +1,1122 @@
+// +build linux,amd64
+
+package devmapper
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "github.com/dotcloud/docker/pkg/label"
+ "github.com/dotcloud/docker/utils"
+ "io"
+ "io/ioutil"
+ "path"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "sync"
+ "syscall"
+ "time"
+)
+
+var (
+ DefaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024
+ DefaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024
+ DefaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024
+)
+
+type DevInfo struct {
+ Hash string `json:"-"`
+ DeviceId int `json:"device_id"`
+ Size uint64 `json:"size"`
+ TransactionId uint64 `json:"transaction_id"`
+ Initialized bool `json:"initialized"`
+ devices *DeviceSet `json:"-"`
+
+ mountCount int `json:"-"`
+ mountPath string `json:"-"`
+ // A floating mount means one reference is not owned and
+ // will be stolen by the next mount. This allows us to
+ // avoid unmounting directly after creation before the
+ // first get (since we need to mount to set up the device
+ // a bit first).
+ floating bool `json:"-"`
+
+ // The global DeviceSet lock guarantees that we serialize all
+ // the calls to libdevmapper (which is not threadsafe), but we
+ // sometimes release that lock while sleeping. In that case
+ // this per-device lock is still held, protecting against
+ // other accesses to the device that we're doing the wait on.
+ //
+ // WARNING: In order to avoid AB-BA deadlocks when releasing
+ // the global lock while holding the per-device locks all
+ // device locks must be aquired *before* the device lock, and
+ // multiple device locks should be aquired parent before child.
+ lock sync.Mutex `json:"-"`
+}
+
+type MetaData struct {
+ Devices map[string]*DevInfo `json:devices`
+ devicesLock sync.Mutex `json:"-"` // Protects all read/writes to Devices map
+}
+
+type DeviceSet struct {
+ MetaData
+ sync.Mutex // Protects Devices map and serializes calls into libdevmapper
+ root string
+ devicePrefix string
+ TransactionId uint64
+ NewTransactionId uint64
+ nextFreeDevice int
+ sawBusy bool
+}
+
+type DiskUsage struct {
+ Used uint64
+ Total uint64
+}
+
+type Status struct {
+ PoolName string
+ DataLoopback string
+ MetadataLoopback string
+ Data DiskUsage
+ Metadata DiskUsage
+ SectorSize uint64
+}
+
+type DevStatus struct {
+ DeviceId int
+ Size uint64
+ TransactionId uint64
+ SizeInSectors uint64
+ MappedSectors uint64
+ HighestMappedSector uint64
+}
+
+type UnmountMode int
+
+const (
+ UnmountRegular UnmountMode = iota
+ UnmountFloat
+ UnmountSink
+)
+
+func getDevName(name string) string {
+ return "/dev/mapper/" + name
+}
+
+func (info *DevInfo) Name() string {
+ hash := info.Hash
+ if hash == "" {
+ hash = "base"
+ }
+ return fmt.Sprintf("%s-%s", info.devices.devicePrefix, hash)
+}
+
+func (info *DevInfo) DevName() string {
+ return getDevName(info.Name())
+}
+
+func (devices *DeviceSet) loopbackDir() string {
+ return path.Join(devices.root, "devicemapper")
+}
+
+func (devices *DeviceSet) jsonFile() string {
+ return path.Join(devices.loopbackDir(), "json")
+}
+
+func (devices *DeviceSet) getPoolName() string {
+ return devices.devicePrefix + "-pool"
+}
+
+func (devices *DeviceSet) getPoolDevName() string {
+ return getDevName(devices.getPoolName())
+}
+
+func (devices *DeviceSet) hasImage(name string) bool {
+ dirname := devices.loopbackDir()
+ filename := path.Join(dirname, name)
+
+ _, err := osStat(filename)
+ return err == nil
+}
+
+// ensureImage creates a sparse file of <size> bytes at the path
+// <root>/devicemapper/<name>.
+// If the file already exists, it does nothing.
+// Either way it returns the full path.
+func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) {
+ dirname := devices.loopbackDir()
+ filename := path.Join(dirname, name)
+
+ if err := osMkdirAll(dirname, 0700); err != nil && !osIsExist(err) {
+ return "", err
+ }
+
+ if _, err := osStat(filename); err != nil {
+ if !osIsNotExist(err) {
+ return "", err
+ }
+ utils.Debugf("Creating loopback file %s for device-manage use", filename)
+ file, err := osOpenFile(filename, osORdWr|osOCreate, 0600)
+ if err != nil {
+ return "", err
+ }
+ defer file.Close()
+
+ if err = file.Truncate(size); err != nil {
+ return "", err
+ }
+ }
+ return filename, nil
+}
+
+func (devices *DeviceSet) allocateDeviceId() int {
+ // TODO: Add smarter reuse of deleted devices
+ id := devices.nextFreeDevice
+ devices.nextFreeDevice = devices.nextFreeDevice + 1
+ return id
+}
+
+func (devices *DeviceSet) allocateTransactionId() uint64 {
+ devices.NewTransactionId = devices.NewTransactionId + 1
+ return devices.NewTransactionId
+}
+
+func (devices *DeviceSet) saveMetadata() error {
+ devices.devicesLock.Lock()
+ jsonData, err := json.Marshal(devices.MetaData)
+ devices.devicesLock.Unlock()
+ if err != nil {
+ return fmt.Errorf("Error encoding metadata to json: %s", err)
+ }
+ tmpFile, err := ioutil.TempFile(filepath.Dir(devices.jsonFile()), ".json")
+ if err != nil {
+ return fmt.Errorf("Error creating metadata file: %s", err)
+ }
+
+ n, err := tmpFile.Write(jsonData)
+ if err != nil {
+ return fmt.Errorf("Error writing metadata to %s: %s", tmpFile.Name(), err)
+ }
+ if n < len(jsonData) {
+ return io.ErrShortWrite
+ }
+ if err := tmpFile.Sync(); err != nil {
+ return fmt.Errorf("Error syncing metadata file %s: %s", tmpFile.Name(), err)
+ }
+ if err := tmpFile.Close(); err != nil {
+ return fmt.Errorf("Error closing metadata file %s: %s", tmpFile.Name(), err)
+ }
+ if err := osRename(tmpFile.Name(), devices.jsonFile()); err != nil {
+ return fmt.Errorf("Error committing metadata file %s: %s", tmpFile.Name(), err)
+ }
+
+ if devices.NewTransactionId != devices.TransactionId {
+ if err = setTransactionId(devices.getPoolDevName(), devices.TransactionId, devices.NewTransactionId); err != nil {
+ return fmt.Errorf("Error setting devmapper transition ID: %s", err)
+ }
+ devices.TransactionId = devices.NewTransactionId
+ }
+ return nil
+}
+
+func (devices *DeviceSet) lookupDevice(hash string) (*DevInfo, error) {
+ devices.devicesLock.Lock()
+ defer devices.devicesLock.Unlock()
+ info := devices.Devices[hash]
+ if info == nil {
+ return nil, fmt.Errorf("Unknown device %s", hash)
+ }
+ return info, nil
+}
+
+func (devices *DeviceSet) registerDevice(id int, hash string, size uint64) (*DevInfo, error) {
+ utils.Debugf("registerDevice(%v, %v)", id, hash)
+ info := &DevInfo{
+ Hash: hash,
+ DeviceId: id,
+ Size: size,
+ TransactionId: devices.allocateTransactionId(),
+ Initialized: false,
+ devices: devices,
+ }
+
+ devices.devicesLock.Lock()
+ devices.Devices[hash] = info
+ devices.devicesLock.Unlock()
+
+ if err := devices.saveMetadata(); err != nil {
+ // Try to remove unused device
+ devices.devicesLock.Lock()
+ delete(devices.Devices, hash)
+ devices.devicesLock.Unlock()
+ return nil, err
+ }
+
+ return info, nil
+}
+
+func (devices *DeviceSet) activateDeviceIfNeeded(info *DevInfo) error {
+ utils.Debugf("activateDeviceIfNeeded(%v)", info.Hash)
+
+ if devinfo, _ := getInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 {
+ return nil
+ }
+
+ return activateDevice(devices.getPoolDevName(), info.Name(), info.DeviceId, info.Size)
+}
+
+func (devices *DeviceSet) createFilesystem(info *DevInfo) error {
+ devname := info.DevName()
+
+ err := execRun("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname)
+ if err != nil {
+ err = execRun("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname)
+ }
+ if err != nil {
+ utils.Debugf("\n--->Err: %s\n", err)
+ return err
+ }
+ return nil
+}
+
+func (devices *DeviceSet) loadMetaData() error {
+ utils.Debugf("loadMetadata()")
+ defer utils.Debugf("loadMetadata END")
+ _, _, _, 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
+
+ jsonData, err := ioutil.ReadFile(devices.jsonFile())
+ if err != nil && !osIsNotExist(err) {
+ utils.Debugf("\n--->Err: %s\n", err)
+ return err
+ }
+
+ devices.MetaData.Devices = make(map[string]*DevInfo)
+ if jsonData != nil {
+ if err := json.Unmarshal(jsonData, &devices.MetaData); err != nil {
+ utils.Debugf("\n--->Err: %s\n", err)
+ return err
+ }
+ }
+
+ for hash, d := range devices.Devices {
+ d.Hash = hash
+ d.devices = devices
+
+ if d.DeviceId >= devices.nextFreeDevice {
+ devices.nextFreeDevice = d.DeviceId + 1
+ }
+
+ // If the transaction id is larger than the actual one we lost the device due to some crash
+ if d.TransactionId > devices.TransactionId {
+ utils.Debugf("Removing lost device %s with id %d", hash, d.TransactionId)
+ delete(devices.Devices, hash)
+ }
+ }
+ return nil
+}
+
+func (devices *DeviceSet) setupBaseImage() error {
+ oldInfo, _ := devices.lookupDevice("")
+ if oldInfo != nil && oldInfo.Initialized {
+ return nil
+ }
+
+ 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
+ }
+ }
+
+ utils.Debugf("Initializing base device-manager snapshot")
+
+ id := devices.allocateDeviceId()
+
+ // Create initial device
+ if err := createDevice(devices.getPoolDevName(), id); err != nil {
+ utils.Debugf("\n--->Err: %s\n", err)
+ return err
+ }
+
+ utils.Debugf("Registering base device (id %v) with FS size %v", id, DefaultBaseFsSize)
+ info, err := devices.registerDevice(id, "", DefaultBaseFsSize)
+ 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(); err != nil {
+ info.Initialized = false
+ utils.Debugf("\n--->Err: %s\n", err)
+ return err
+ }
+
+ return nil
+}
+
+func setCloseOnExec(name string) {
+ if fileInfos, _ := ioutil.ReadDir("/proc/self/fd"); fileInfos != nil {
+ for _, i := range fileInfos {
+ link, _ := osReadlink(filepath.Join("/proc/self/fd", i.Name()))
+ if link == name {
+ fd, err := strconv.Atoi(i.Name())
+ if err == nil {
+ sysCloseOnExec(fd)
+ }
+ }
+ }
+ }
+}
+
+func (devices *DeviceSet) log(level int, file string, line int, dmError int, message string) {
+ if level >= 7 {
+ return // Ignore _LOG_DEBUG
+ }
+
+ if strings.Contains(message, "busy") {
+ devices.sawBusy = true
+ }
+
+ utils.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
+}
+
+func major(device uint64) uint64 {
+ return (device >> 8) & 0xfff
+}
+
+func minor(device uint64) uint64 {
+ return (device & 0xff) | ((device >> 12) & 0xfff00)
+}
+
+func (devices *DeviceSet) ResizePool(size int64) error {
+ dirname := devices.loopbackDir()
+ datafilename := path.Join(dirname, "data")
+ metadatafilename := path.Join(dirname, "metadata")
+
+ datafile, err := osOpenFile(datafilename, osORdWr, 0)
+ if datafile == nil {
+ return err
+ }
+ defer datafile.Close()
+
+ fi, err := datafile.Stat()
+ if fi == nil {
+ return err
+ }
+
+ if fi.Size() > size {
+ return fmt.Errorf("Can't shrink file")
+ }
+
+ dataloopback := FindLoopDeviceFor(datafile)
+ if dataloopback == nil {
+ return fmt.Errorf("Unable to find loopback mount for: %s", datafilename)
+ }
+ defer dataloopback.Close()
+
+ metadatafile, err := osOpenFile(metadatafilename, osORdWr, 0)
+ if metadatafile == nil {
+ return err
+ }
+ defer metadatafile.Close()
+
+ metadataloopback := FindLoopDeviceFor(metadatafile)
+ if metadataloopback == nil {
+ return fmt.Errorf("Unable to find loopback mount for: %s", metadatafilename)
+ }
+ defer metadataloopback.Close()
+
+ // Grow loopback file
+ if err := datafile.Truncate(size); err != nil {
+ return fmt.Errorf("Unable to grow loopback file: %s", err)
+ }
+
+ // Reload size for loopback device
+ if err := LoopbackSetCapacity(dataloopback); err != nil {
+ return fmt.Errorf("Unable to update loopback capacity: %s", err)
+ }
+
+ // Suspend the pool
+ if err := suspendDevice(devices.getPoolName()); err != nil {
+ return fmt.Errorf("Unable to suspend pool: %s", err)
+ }
+
+ // Reload with the new block sizes
+ if err := reloadPool(devices.getPoolName(), dataloopback, metadataloopback); err != nil {
+ return fmt.Errorf("Unable to reload pool: %s", err)
+ }
+
+ // Resume the pool
+ if err := resumeDevice(devices.getPoolName()); err != nil {
+ return fmt.Errorf("Unable to resume pool: %s", err)
+ }
+
+ return nil
+}
+
+func (devices *DeviceSet) initDevmapper(doInit bool) error {
+ logInit(devices)
+
+ // Make sure the sparse images exist in <root>/devicemapper/data and
+ // <root>/devicemapper/metadata
+
+ hasData := devices.hasImage("data")
+ hasMetadata := devices.hasImage("metadata")
+
+ if !doInit && !hasData {
+ return errors.New("Loopback data file not found")
+ }
+
+ if !doInit && !hasMetadata {
+ return errors.New("Loopback metadata file not found")
+ }
+
+ 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
+ }
+
+ // Set the device prefix from the device id and inode of the docker root dir
+
+ st, err := osStat(devices.root)
+ if err != nil {
+ return fmt.Errorf("Error looking up dir %s: %s", devices.root, err)
+ }
+ sysSt := toSysStatT(st.Sys())
+ // "reg-" stands for "regular file".
+ // In the future we might use "dev-" for "device file", etc.
+ // docker-maj,min[-inode] stands for:
+ // - Managed by docker
+ // - The target of this device is at major <maj> and minor <min>
+ // - If <inode> is defined, use that file inside the device as a loopback image. Otherwise use the device itself.
+ devices.devicePrefix = fmt.Sprintf("docker-%d:%d-%d", major(sysSt.Dev), minor(sysSt.Dev), sysSt.Ino)
+ utils.Debugf("Generated prefix: %s", devices.devicePrefix)
+
+ // Check for the existence of the device <prefix>-pool
+ utils.Debugf("Checking for existence of the pool '%s'", devices.getPoolName())
+ info, err := getInfo(devices.getPoolName())
+ if info == nil {
+ utils.Debugf("Error device getInfo: %s", err)
+ return err
+ }
+
+ // It seems libdevmapper opens this without O_CLOEXEC, and go exec will not close files
+ // that are not Close-on-exec, and lxc-start will die if it inherits any unexpected files,
+ // so we add this badhack to make sure it closes itself
+ setCloseOnExec("/dev/mapper/control")
+
+ // If the pool doesn't exist, create it
+ if info.Exists == 0 {
+ utils.Debugf("Pool doesn't exist. Creating it.")
+
+ dataFile, err := attachLoopDevice(data)
+ if err != nil {
+ utils.Debugf("\n--->Err: %s\n", err)
+ return err
+ }
+ defer dataFile.Close()
+
+ metadataFile, err := attachLoopDevice(metadata)
+ if err != nil {
+ utils.Debugf("\n--->Err: %s\n", err)
+ return err
+ }
+ defer metadataFile.Close()
+
+ if err := createPool(devices.getPoolName(), dataFile, metadataFile); err != nil {
+ utils.Debugf("\n--->Err: %s\n", err)
+ return err
+ }
+ }
+
+ // If we didn't just create the data or metadata image, we need to
+ // load the metadata from the existing file.
+ if !createdLoopback {
+ if err = devices.loadMetaData(); err != nil {
+ utils.Debugf("\n--->Err: %s\n", err)
+ return err
+ }
+ }
+
+ // Setup the base image
+ if doInit {
+ if err := devices.setupBaseImage(); err != nil {
+ utils.Debugf("Error device setupBaseImage: %s\n", err)
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (devices *DeviceSet) AddDevice(hash, baseHash string) error {
+ baseInfo, err := devices.lookupDevice(baseHash)
+ if err != nil {
+ return err
+ }
+
+ baseInfo.lock.Lock()
+ defer baseInfo.lock.Unlock()
+
+ devices.Lock()
+ defer devices.Unlock()
+
+ if info, _ := devices.lookupDevice(hash); info != nil {
+ return fmt.Errorf("device %s already exists", hash)
+ }
+
+ deviceId := devices.allocateDeviceId()
+
+ if err := devices.createSnapDevice(devices.getPoolDevName(), deviceId, baseInfo.Name(), baseInfo.DeviceId); err != nil {
+ utils.Debugf("Error creating snap device: %s\n", err)
+ return err
+ }
+
+ if _, err := devices.registerDevice(deviceId, hash, baseInfo.Size); err != nil {
+ deleteDevice(devices.getPoolDevName(), deviceId)
+ utils.Debugf("Error registering device: %s\n", err)
+ return err
+ }
+ return nil
+}
+
+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)
+ }
+ }
+
+ devinfo, _ := getInfo(info.Name())
+ if devinfo != nil && devinfo.Exists != 0 {
+ if err := devices.removeDeviceAndWait(info.Name()); err != nil {
+ utils.Debugf("Error removing device: %s\n", err)
+ return err
+ }
+ }
+
+ if info.Initialized {
+ info.Initialized = false
+ if err := devices.saveMetadata(); err != nil {
+ utils.Debugf("Error saving meta data: %s\n", err)
+ return err
+ }
+ }
+
+ if err := deleteDevice(devices.getPoolDevName(), info.DeviceId); err != nil {
+ utils.Debugf("Error deleting device: %s\n", err)
+ return err
+ }
+
+ devices.allocateTransactionId()
+ devices.devicesLock.Lock()
+ delete(devices.Devices, info.Hash)
+ devices.devicesLock.Unlock()
+
+ if err := devices.saveMetadata(); err != nil {
+ devices.devicesLock.Lock()
+ devices.Devices[info.Hash] = info
+ devices.devicesLock.Unlock()
+ utils.Debugf("Error saving meta data: %s\n", err)
+ return err
+ }
+
+ return nil
+}
+
+func (devices *DeviceSet) DeleteDevice(hash string) error {
+ info, err := devices.lookupDevice(hash)
+ if err != nil {
+ return err
+ }
+
+ info.lock.Lock()
+ defer info.lock.Unlock()
+
+ devices.Lock()
+ defer devices.Unlock()
+
+ return devices.deleteDevice(info)
+}
+
+func (devices *DeviceSet) deactivatePool() error {
+ utils.Debugf("[devmapper] deactivatePool()")
+ defer utils.Debugf("[devmapper] deactivatePool END")
+ devname := devices.getPoolDevName()
+ devinfo, err := getInfo(devname)
+ if err != nil {
+ utils.Debugf("\n--->Err: %s\n", err)
+ return err
+ }
+ if devinfo.Exists != 0 {
+ return removeDevice(devname)
+ }
+
+ return nil
+}
+
+func (devices *DeviceSet) deactivateDevice(info *DevInfo) error {
+ utils.Debugf("[devmapper] deactivateDevice(%s)", info.Hash)
+ defer utils.Debugf("[devmapper] deactivateDevice END")
+
+ // Wait for the unmount to be effective,
+ // by watching the value of Info.OpenCount for the device
+ if err := devices.waitClose(info); err != nil {
+ utils.Errorf("Warning: error waiting for device %s to close: %s\n", info.Hash, err)
+ }
+
+ 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
+ }
+ }
+
+ return nil
+}
+
+// Issues the underlying dm remove operation and then waits
+// for it to finish.
+func (devices *DeviceSet) removeDeviceAndWait(devname string) error {
+ var err error
+
+ for i := 0; i < 1000; i++ {
+ devices.sawBusy = false
+ err = removeDevice(devname)
+ if err == nil {
+ break
+ }
+ if !devices.sawBusy {
+ return err
+ }
+
+ // If we see EBUSY it may be a transient error,
+ // sleep a bit a retry a few times.
+ devices.Unlock()
+ time.Sleep(10 * time.Millisecond)
+ devices.Lock()
+ }
+ if err != nil {
+ return err
+ }
+
+ if err := devices.waitRemove(devname); err != nil {
+ return err
+ }
+ return nil
+}
+
+// waitRemove blocks until either:
+// a) the device registered at <device_set_prefix>-<hash> is removed,
+// or b) the 10 second timeout expires.
+func (devices *DeviceSet) waitRemove(devname string) error {
+ utils.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, devname)
+ defer utils.Debugf("[deviceset %s] waitRemove(%s) END", devices.devicePrefix, devname)
+ i := 0
+ for ; i < 1000; i += 1 {
+ devinfo, err := getInfo(devname)
+ if err != nil {
+ // If there is an error we assume the device doesn't exist.
+ // The error might actually be something else, but we can't differentiate.
+ return nil
+ }
+ if i%100 == 0 {
+ utils.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists)
+ }
+ if devinfo.Exists == 0 {
+ break
+ }
+
+ devices.Unlock()
+ time.Sleep(10 * time.Millisecond)
+ devices.Lock()
+ }
+ if i == 1000 {
+ return fmt.Errorf("Timeout while waiting for device %s to be removed", devname)
+ }
+ return nil
+}
+
+// waitClose blocks until either:
+// a) the device registered at <device_set_prefix>-<hash> is closed,
+// or b) the 10 second timeout expires.
+func (devices *DeviceSet) waitClose(info *DevInfo) error {
+ i := 0
+ for ; i < 1000; i += 1 {
+ devinfo, err := getInfo(info.Name())
+ if err != nil {
+ return err
+ }
+ if i%100 == 0 {
+ utils.Debugf("Waiting for unmount of %s: opencount=%d", info.Hash, devinfo.OpenCount)
+ }
+ if devinfo.OpenCount == 0 {
+ break
+ }
+ devices.Unlock()
+ time.Sleep(10 * time.Millisecond)
+ devices.Lock()
+ }
+ if i == 1000 {
+ return fmt.Errorf("Timeout while waiting for device %s to close", info.Hash)
+ }
+ return nil
+}
+
+func (devices *DeviceSet) Shutdown() error {
+
+ utils.Debugf("[deviceset %s] shutdown()", devices.devicePrefix)
+ utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root)
+ defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix)
+
+ var devs []*DevInfo
+
+ devices.devicesLock.Lock()
+ for _, info := range devices.Devices {
+ devs = append(devs, info)
+ }
+ devices.devicesLock.Unlock()
+
+ for _, info := range devs {
+ info.lock.Lock()
+ if info.mountCount > 0 {
+ // We use MNT_DETACH here in case it is still busy in some running
+ // container. This means it'll go away from the global scope directly,
+ // and the device will be released when that container dies.
+ if err := sysUnmount(info.mountPath, syscall.MNT_DETACH); err != nil {
+ utils.Debugf("Shutdown unmounting %s, error: %s\n", info.mountPath, err)
+ }
+
+ devices.Lock()
+ if err := devices.deactivateDevice(info); err != nil {
+ utils.Debugf("Shutdown deactivate %s , error: %s\n", info.Hash, err)
+ }
+ devices.Unlock()
+ }
+ info.lock.Unlock()
+ }
+
+ info, _ := devices.lookupDevice("")
+ if info != nil {
+ info.lock.Lock()
+ devices.Lock()
+ if err := devices.deactivateDevice(info); err != nil {
+ utils.Debugf("Shutdown deactivate base , error: %s\n", err)
+ }
+ devices.Unlock()
+ info.lock.Unlock()
+ }
+
+ devices.Lock()
+ if err := devices.deactivatePool(); err != nil {
+ utils.Debugf("Shutdown deactivate pool , error: %s\n", err)
+ }
+ devices.Unlock()
+
+ return nil
+}
+
+func (devices *DeviceSet) MountDevice(hash, path string, mountLabel string) error {
+ info, err := devices.lookupDevice(hash)
+ if err != nil {
+ return err
+ }
+
+ info.lock.Lock()
+ defer info.lock.Unlock()
+
+ devices.Lock()
+ defer devices.Unlock()
+
+ if info.mountCount > 0 {
+ if path != info.mountPath {
+ return fmt.Errorf("Trying to mount devmapper device in multple places (%s, %s)", info.mountPath, path)
+ }
+
+ if info.floating {
+ // Steal floating ref
+ info.floating = false
+ } else {
+ info.mountCount++
+ }
+ return nil
+ }
+
+ if err := devices.activateDeviceIfNeeded(info); err != nil {
+ return fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err)
+ }
+
+ var flags uintptr = sysMsMgcVal
+
+ mountOptions := label.FormatMountLabel("discard", mountLabel)
+ err = sysMount(info.DevName(), path, "ext4", flags, mountOptions)
+ if err != nil && err == sysEInval {
+ mountOptions = label.FormatMountLabel(mountLabel, "")
+ err = sysMount(info.DevName(), path, "ext4", flags, mountOptions)
+ }
+ if err != nil {
+ return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err)
+ }
+
+ info.mountCount = 1
+ info.mountPath = path
+ info.floating = false
+
+ return devices.setInitialized(info)
+}
+
+func (devices *DeviceSet) UnmountDevice(hash string, mode UnmountMode) error {
+ utils.Debugf("[devmapper] UnmountDevice(hash=%s, mode=%d)", hash, mode)
+ defer utils.Debugf("[devmapper] UnmountDevice END")
+
+ info, err := devices.lookupDevice(hash)
+ if err != nil {
+ return err
+ }
+
+ info.lock.Lock()
+ defer info.lock.Unlock()
+
+ devices.Lock()
+ defer devices.Unlock()
+
+ if mode == UnmountFloat {
+ if info.floating {
+ return fmt.Errorf("UnmountDevice: can't float floating reference %s\n", hash)
+ }
+
+ // Leave this reference floating
+ info.floating = true
+ return nil
+ }
+
+ if mode == UnmountSink {
+ if !info.floating {
+ // Someone already sunk this
+ return nil
+ }
+ // Otherwise, treat this as a regular unmount
+ }
+
+ if info.mountCount == 0 {
+ return fmt.Errorf("UnmountDevice: device not-mounted id %s\n", hash)
+ }
+
+ info.mountCount--
+ if info.mountCount > 0 {
+ return nil
+ }
+
+ utils.Debugf("[devmapper] Unmount(%s)", info.mountPath)
+ if err := sysUnmount(info.mountPath, 0); err != nil {
+ utils.Debugf("\n--->Err: %s\n", err)
+ return err
+ }
+ utils.Debugf("[devmapper] Unmount done")
+
+ if err := devices.deactivateDevice(info); err != nil {
+ return err
+ }
+
+ info.mountPath = ""
+
+ return nil
+}
+
+func (devices *DeviceSet) HasDevice(hash string) bool {
+ devices.Lock()
+ defer devices.Unlock()
+
+ info, _ := devices.lookupDevice(hash)
+ return info != nil
+}
+
+func (devices *DeviceSet) HasInitializedDevice(hash string) bool {
+ devices.Lock()
+ defer devices.Unlock()
+
+ info, _ := devices.lookupDevice(hash)
+ return info != nil && info.Initialized
+}
+
+func (devices *DeviceSet) HasActivatedDevice(hash string) bool {
+ info, _ := devices.lookupDevice(hash)
+ if info == nil {
+ return false
+ }
+
+ info.lock.Lock()
+ defer info.lock.Unlock()
+
+ devices.Lock()
+ defer devices.Unlock()
+
+ devinfo, _ := getInfo(info.Name())
+ return devinfo != nil && devinfo.Exists != 0
+}
+
+func (devices *DeviceSet) setInitialized(info *DevInfo) error {
+ info.Initialized = true
+ if err := devices.saveMetadata(); err != nil {
+ info.Initialized = false
+ utils.Debugf("\n--->Err: %s\n", err)
+ return err
+ }
+
+ return nil
+}
+
+func (devices *DeviceSet) List() []string {
+ devices.Lock()
+ defer devices.Unlock()
+
+ devices.devicesLock.Lock()
+ ids := make([]string, len(devices.Devices))
+ i := 0
+ for k := range devices.Devices {
+ ids[i] = k
+ i++
+ }
+ devices.devicesLock.Unlock()
+
+ return ids
+}
+
+func (devices *DeviceSet) deviceStatus(devName string) (sizeInSectors, mappedSectors, highestMappedSector uint64, err error) {
+ var params string
+ _, sizeInSectors, _, params, err = getStatus(devName)
+ if err != nil {
+ return
+ }
+ if _, err = fmt.Sscanf(params, "%d %d", &mappedSectors, &highestMappedSector); err == nil {
+ return
+ }
+ return
+}
+
+func (devices *DeviceSet) GetDeviceStatus(hash string) (*DevStatus, error) {
+ info, err := devices.lookupDevice(hash)
+ if err != nil {
+ return nil, err
+ }
+
+ info.lock.Lock()
+ defer info.lock.Unlock()
+
+ devices.Lock()
+ defer devices.Unlock()
+
+ status := &DevStatus{
+ DeviceId: info.DeviceId,
+ Size: info.Size,
+ TransactionId: info.TransactionId,
+ }
+
+ if err := devices.activateDeviceIfNeeded(info); err != nil {
+ return nil, fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err)
+ }
+
+ if sizeInSectors, mappedSectors, highestMappedSector, err := devices.deviceStatus(info.DevName()); err != nil {
+ return nil, err
+ } else {
+ status.SizeInSectors = sizeInSectors
+ status.MappedSectors = mappedSectors
+ status.HighestMappedSector = highestMappedSector
+ }
+
+ return status, nil
+}
+
+func (devices *DeviceSet) poolStatus() (totalSizeInSectors, transactionId, dataUsed, dataTotal, metadataUsed, metadataTotal uint64, err error) {
+ var params string
+ if _, totalSizeInSectors, _, params, err = getStatus(devices.getPoolName()); err == nil {
+ _, err = fmt.Sscanf(params, "%d %d/%d %d/%d", &transactionId, &metadataUsed, &metadataTotal, &dataUsed, &dataTotal)
+ }
+ return
+}
+
+func (devices *DeviceSet) Status() *Status {
+ devices.Lock()
+ defer devices.Unlock()
+
+ status := &Status{}
+
+ status.PoolName = devices.getPoolName()
+ status.DataLoopback = path.Join(devices.loopbackDir(), "data")
+ status.MetadataLoopback = path.Join(devices.loopbackDir(), "metadata")
+
+ totalSizeInSectors, _, dataUsed, dataTotal, metadataUsed, metadataTotal, err := devices.poolStatus()
+ if err == nil {
+ // Convert from blocks to bytes
+ blockSizeInSectors := totalSizeInSectors / dataTotal
+
+ status.Data.Used = dataUsed * blockSizeInSectors * 512
+ status.Data.Total = dataTotal * blockSizeInSectors * 512
+
+ // metadata blocks are always 4k
+ status.Metadata.Used = metadataUsed * 4096
+ status.Metadata.Total = metadataTotal * 4096
+
+ status.SectorSize = blockSizeInSectors * 512
+ }
+
+ return status
+}
+
+func NewDeviceSet(root string, doInit bool) (*DeviceSet, error) {
+ SetDevDir("/dev")
+
+ devices := &DeviceSet{
+ root: root,
+ MetaData: MetaData{Devices: make(map[string]*DevInfo)},
+ }
+
+ if err := devices.initDevmapper(doInit); err != nil {
+ return nil, err
+ }
+
+ return devices, nil
+}
diff --git a/runtime/graphdriver/devmapper/devmapper.go b/runtime/graphdriver/devmapper/devmapper.go
new file mode 100644
index 0000000000..7317118dcf
--- /dev/null
+++ b/runtime/graphdriver/devmapper/devmapper.go
@@ -0,0 +1,595 @@
+// +build linux,amd64
+
+package devmapper
+
+import (
+ "errors"
+ "fmt"
+ "github.com/dotcloud/docker/utils"
+ "runtime"
+ "syscall"
+)
+
+type DevmapperLogger interface {
+ log(level int, file string, line int, dmError int, message string)
+}
+
+const (
+ DeviceCreate TaskType = iota
+ DeviceReload
+ DeviceRemove
+ DeviceRemoveAll
+ DeviceSuspend
+ DeviceResume
+ DeviceInfo
+ DeviceDeps
+ DeviceRename
+ DeviceVersion
+ DeviceStatus
+ DeviceTable
+ DeviceWaitevent
+ DeviceList
+ DeviceClear
+ DeviceMknodes
+ DeviceListVersions
+ DeviceTargetMsg
+ DeviceSetGeometry
+)
+
+const (
+ AddNodeOnResume AddNodeType = iota
+ AddNodeOnCreate
+)
+
+var (
+ ErrTaskRun = errors.New("dm_task_run failed")
+ ErrTaskSetName = errors.New("dm_task_set_name failed")
+ ErrTaskSetMessage = errors.New("dm_task_set_message failed")
+ ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed")
+ ErrTaskSetRo = errors.New("dm_task_set_ro failed")
+ ErrTaskAddTarget = errors.New("dm_task_add_target failed")
+ ErrTaskSetSector = errors.New("dm_task_set_sector failed")
+ ErrTaskGetInfo = errors.New("dm_task_get_info failed")
+ ErrTaskSetCookie = errors.New("dm_task_set_cookie failed")
+ ErrNilCookie = errors.New("cookie ptr can't be nil")
+ ErrAttachLoopbackDevice = errors.New("loopback mounting failed")
+ ErrGetBlockSize = errors.New("Can't get block size")
+ ErrUdevWait = errors.New("wait on udev cookie failed")
+ ErrSetDevDir = errors.New("dm_set_dev_dir failed")
+ ErrGetLibraryVersion = errors.New("dm_get_library_version failed")
+ ErrCreateRemoveTask = errors.New("Can't create task of type DeviceRemove")
+ ErrRunRemoveDevice = errors.New("running removeDevice failed")
+ ErrInvalidAddNode = errors.New("Invalide AddNoce type")
+ ErrGetLoopbackBackingFile = errors.New("Unable to get loopback backing file")
+ ErrLoopbackSetCapacity = errors.New("Unable set loopback capacity")
+)
+
+type (
+ Task struct {
+ unmanaged *CDmTask
+ }
+ Info struct {
+ Exists int
+ Suspended int
+ LiveTable int
+ InactiveTable int
+ OpenCount int32
+ EventNr uint32
+ Major uint32
+ Minor uint32
+ ReadOnly int
+ TargetCount int32
+ }
+ TaskType int
+ AddNodeType int
+)
+
+func (t *Task) destroy() {
+ if t != nil {
+ DmTaskDestroy(t.unmanaged)
+ runtime.SetFinalizer(t, nil)
+ }
+}
+
+func TaskCreate(tasktype TaskType) *Task {
+ Ctask := DmTaskCreate(int(tasktype))
+ if Ctask == nil {
+ return nil
+ }
+ task := &Task{unmanaged: Ctask}
+ runtime.SetFinalizer(task, (*Task).destroy)
+ return task
+}
+
+func (t *Task) Run() error {
+ if res := DmTaskRun(t.unmanaged); res != 1 {
+ return ErrTaskRun
+ }
+ return nil
+}
+
+func (t *Task) SetName(name string) error {
+ if res := DmTaskSetName(t.unmanaged, name); res != 1 {
+ return ErrTaskSetName
+ }
+ return nil
+}
+
+func (t *Task) SetMessage(message string) error {
+ if res := DmTaskSetMessage(t.unmanaged, message); res != 1 {
+ return ErrTaskSetMessage
+ }
+ return nil
+}
+
+func (t *Task) SetSector(sector uint64) error {
+ if res := DmTaskSetSector(t.unmanaged, sector); res != 1 {
+ return ErrTaskSetSector
+ }
+ return nil
+}
+
+func (t *Task) SetCookie(cookie *uint, flags uint16) error {
+ if cookie == nil {
+ return ErrNilCookie
+ }
+ if res := DmTaskSetCookie(t.unmanaged, cookie, flags); res != 1 {
+ return ErrTaskSetCookie
+ }
+ return nil
+}
+
+func (t *Task) SetAddNode(addNode AddNodeType) error {
+ if addNode != AddNodeOnResume && addNode != AddNodeOnCreate {
+ return ErrInvalidAddNode
+ }
+ if res := DmTaskSetAddNode(t.unmanaged, addNode); res != 1 {
+ return ErrTaskSetAddNode
+ }
+ return nil
+}
+
+func (t *Task) SetRo() error {
+ if res := DmTaskSetRo(t.unmanaged); res != 1 {
+ return ErrTaskSetRo
+ }
+ return nil
+}
+
+func (t *Task) AddTarget(start, size uint64, ttype, params string) error {
+ if res := DmTaskAddTarget(t.unmanaged, start, size,
+ ttype, params); res != 1 {
+ return ErrTaskAddTarget
+ }
+ return nil
+}
+
+func (t *Task) GetInfo() (*Info, error) {
+ info := &Info{}
+ if res := DmTaskGetInfo(t.unmanaged, info); res != 1 {
+ return nil, ErrTaskGetInfo
+ }
+ return info, nil
+}
+
+func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64,
+ length uint64, targetType string, params string) {
+
+ return DmGetNextTarget(t.unmanaged, next, &start, &length,
+ &targetType, &params),
+ start, length, targetType, params
+}
+
+func getLoopbackBackingFile(file *osFile) (uint64, uint64, error) {
+ loopInfo, err := ioctlLoopGetStatus64(file.Fd())
+ if err != nil {
+ utils.Errorf("Error get loopback backing file: %s\n", err)
+ return 0, 0, ErrGetLoopbackBackingFile
+ }
+ return loopInfo.loDevice, loopInfo.loInode, nil
+}
+
+func LoopbackSetCapacity(file *osFile) error {
+ if err := ioctlLoopSetCapacity(file.Fd(), 0); err != nil {
+ utils.Errorf("Error loopbackSetCapacity: %s", err)
+ return ErrLoopbackSetCapacity
+ }
+ return nil
+}
+
+func FindLoopDeviceFor(file *osFile) *osFile {
+ stat, err := file.Stat()
+ if err != nil {
+ return nil
+ }
+ targetInode := stat.Sys().(*sysStatT).Ino
+ targetDevice := stat.Sys().(*sysStatT).Dev
+
+ for i := 0; true; i++ {
+ path := fmt.Sprintf("/dev/loop%d", i)
+
+ file, err := osOpenFile(path, osORdWr, 0)
+ if err != nil {
+ if osIsNotExist(err) {
+ return nil
+ }
+
+ // Ignore all errors until the first not-exist
+ // we want to continue looking for the file
+ continue
+ }
+
+ dev, inode, err := getLoopbackBackingFile(file)
+ if err == nil && dev == targetDevice && inode == targetInode {
+ return file
+ }
+ file.Close()
+ }
+
+ return nil
+}
+
+func UdevWait(cookie uint) error {
+ if res := DmUdevWait(cookie); res != 1 {
+ utils.Debugf("Failed to wait on udev cookie %d", cookie)
+ return ErrUdevWait
+ }
+ return nil
+}
+
+func LogInitVerbose(level int) {
+ DmLogInitVerbose(level)
+}
+
+var dmLogger DevmapperLogger = nil
+
+func logInit(logger DevmapperLogger) {
+ dmLogger = logger
+ LogWithErrnoInit()
+}
+
+func SetDevDir(dir string) error {
+ if res := DmSetDevDir(dir); res != 1 {
+ utils.Debugf("Error dm_set_dev_dir")
+ return ErrSetDevDir
+ }
+ return nil
+}
+
+func GetLibraryVersion() (string, error) {
+ var version string
+ if res := DmGetLibraryVersion(&version); res != 1 {
+ return "", ErrGetLibraryVersion
+ }
+ return version, nil
+}
+
+// Useful helper for cleanup
+func RemoveDevice(name string) error {
+ task := TaskCreate(DeviceRemove)
+ if task == nil {
+ return ErrCreateRemoveTask
+ }
+ if err := task.SetName(name); err != nil {
+ utils.Debugf("Can't set task name %s", name)
+ return err
+ }
+ if err := task.Run(); err != nil {
+ return ErrRunRemoveDevice
+ }
+ return nil
+}
+
+func GetBlockDeviceSize(file *osFile) (uint64, error) {
+ size, err := ioctlBlkGetSize64(file.Fd())
+ if err != nil {
+ utils.Errorf("Error getblockdevicesize: %s", err)
+ return 0, ErrGetBlockSize
+ }
+ return uint64(size), nil
+}
+
+func BlockDeviceDiscard(path string) error {
+ file, err := osOpenFile(path, osORdWr, 0)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ size, err := GetBlockDeviceSize(file)
+ if err != nil {
+ return err
+ }
+
+ if err := ioctlBlkDiscard(file.Fd(), 0, size); err != nil {
+ return err
+ }
+
+ // Without this sometimes the remove of the device that happens after
+ // discard fails with EBUSY.
+ syscall.Sync()
+
+ return nil
+}
+
+// This is the programmatic example of "dmsetup create"
+func createPool(poolName string, dataFile, metadataFile *osFile) error {
+ task, err := createTask(DeviceCreate, poolName)
+ if task == nil {
+ return err
+ }
+
+ size, err := GetBlockDeviceSize(dataFile)
+ if err != nil {
+ return fmt.Errorf("Can't get data size")
+ }
+
+ params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768 1 skip_block_zeroing"
+ if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil {
+ return fmt.Errorf("Can't add target")
+ }
+
+ var cookie uint = 0
+ if err := task.SetCookie(&cookie, 0); err != nil {
+ return fmt.Errorf("Can't set cookie")
+ }
+
+ if err := task.Run(); err != nil {
+ return fmt.Errorf("Error running DeviceCreate (createPool)")
+ }
+
+ UdevWait(cookie)
+
+ return nil
+}
+
+func reloadPool(poolName string, dataFile, metadataFile *osFile) error {
+ task, err := createTask(DeviceReload, poolName)
+ if task == nil {
+ return err
+ }
+
+ size, err := GetBlockDeviceSize(dataFile)
+ if err != nil {
+ return fmt.Errorf("Can't get data size")
+ }
+
+ params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768"
+ if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil {
+ return fmt.Errorf("Can't add target")
+ }
+
+ if err := task.Run(); err != nil {
+ return fmt.Errorf("Error running DeviceCreate")
+ }
+
+ return nil
+}
+
+func createTask(t TaskType, name string) (*Task, error) {
+ task := TaskCreate(t)
+ if task == nil {
+ return nil, fmt.Errorf("Can't create task of type %d", int(t))
+ }
+ if err := task.SetName(name); err != nil {
+ return nil, fmt.Errorf("Can't set task name %s", name)
+ }
+ return task, nil
+}
+
+func getInfo(name string) (*Info, error) {
+ task, err := createTask(DeviceInfo, name)
+ if task == nil {
+ return nil, err
+ }
+ if err := task.Run(); err != nil {
+ return nil, err
+ }
+ return task.GetInfo()
+}
+
+func getStatus(name string) (uint64, uint64, string, string, error) {
+ task, err := createTask(DeviceStatus, name)
+ if task == nil {
+ utils.Debugf("getStatus: Error createTask: %s", err)
+ return 0, 0, "", "", err
+ }
+ if err := task.Run(); err != nil {
+ utils.Debugf("getStatus: Error Run: %s", err)
+ return 0, 0, "", "", err
+ }
+
+ devinfo, err := task.GetInfo()
+ if err != nil {
+ utils.Debugf("getStatus: Error GetInfo: %s", err)
+ return 0, 0, "", "", err
+ }
+ if devinfo.Exists == 0 {
+ utils.Debugf("getStatus: Non existing device %s", name)
+ return 0, 0, "", "", fmt.Errorf("Non existing device %s", name)
+ }
+
+ _, start, length, targetType, params := task.GetNextTarget(0)
+ return start, length, targetType, params, nil
+}
+
+func setTransactionId(poolName string, oldId uint64, newId uint64) error {
+ task, err := createTask(DeviceTargetMsg, poolName)
+ if task == nil {
+ return err
+ }
+
+ if err := task.SetSector(0); err != nil {
+ return fmt.Errorf("Can't set sector")
+ }
+
+ if err := task.SetMessage(fmt.Sprintf("set_transaction_id %d %d", oldId, newId)); err != nil {
+ return fmt.Errorf("Can't set message")
+ }
+
+ if err := task.Run(); err != nil {
+ return fmt.Errorf("Error running setTransactionId")
+ }
+ return nil
+}
+
+func suspendDevice(name string) error {
+ task, err := createTask(DeviceSuspend, name)
+ if task == nil {
+ return err
+ }
+ if err := task.Run(); err != nil {
+ return fmt.Errorf("Error running DeviceSuspend: %s", err)
+ }
+ return nil
+}
+
+func resumeDevice(name string) error {
+ task, err := createTask(DeviceResume, name)
+ if task == nil {
+ return err
+ }
+
+ var cookie uint = 0
+ if err := task.SetCookie(&cookie, 0); err != nil {
+ return fmt.Errorf("Can't set cookie")
+ }
+
+ if err := task.Run(); err != nil {
+ return fmt.Errorf("Error running DeviceResume")
+ }
+
+ UdevWait(cookie)
+
+ return nil
+}
+
+func createDevice(poolName string, deviceId int) error {
+ utils.Debugf("[devmapper] createDevice(poolName=%v, deviceId=%v)", poolName, deviceId)
+ task, err := createTask(DeviceTargetMsg, poolName)
+ if task == nil {
+ return err
+ }
+
+ if err := task.SetSector(0); err != nil {
+ return fmt.Errorf("Can't set sector")
+ }
+
+ if err := task.SetMessage(fmt.Sprintf("create_thin %d", deviceId)); err != nil {
+ return fmt.Errorf("Can't set message")
+ }
+
+ if err := task.Run(); err != nil {
+ return fmt.Errorf("Error running createDevice")
+ }
+ return nil
+}
+
+func deleteDevice(poolName string, deviceId int) error {
+ task, err := createTask(DeviceTargetMsg, poolName)
+ if task == nil {
+ return err
+ }
+
+ if err := task.SetSector(0); err != nil {
+ return fmt.Errorf("Can't set sector")
+ }
+
+ if err := task.SetMessage(fmt.Sprintf("delete %d", deviceId)); err != nil {
+ return fmt.Errorf("Can't set message")
+ }
+
+ if err := task.Run(); err != nil {
+ return fmt.Errorf("Error running deleteDevice")
+ }
+ return nil
+}
+
+func removeDevice(name string) error {
+ utils.Debugf("[devmapper] removeDevice START")
+ defer utils.Debugf("[devmapper] removeDevice END")
+ task, err := createTask(DeviceRemove, name)
+ if task == nil {
+ return err
+ }
+ if err = task.Run(); err != nil {
+ return fmt.Errorf("Error running removeDevice")
+ }
+ return nil
+}
+
+func activateDevice(poolName string, name string, deviceId int, size uint64) error {
+ task, err := createTask(DeviceCreate, name)
+ if task == nil {
+ return err
+ }
+
+ params := fmt.Sprintf("%s %d", poolName, deviceId)
+ if err := task.AddTarget(0, size/512, "thin", params); err != nil {
+ return fmt.Errorf("Can't add target")
+ }
+ if err := task.SetAddNode(AddNodeOnCreate); err != nil {
+ return fmt.Errorf("Can't add node")
+ }
+
+ var cookie uint = 0
+ if err := task.SetCookie(&cookie, 0); err != nil {
+ return fmt.Errorf("Can't set cookie")
+ }
+
+ if err := task.Run(); err != nil {
+ return fmt.Errorf("Error running DeviceCreate (activateDevice)")
+ }
+
+ UdevWait(cookie)
+
+ return nil
+}
+
+func (devices *DeviceSet) createSnapDevice(poolName string, deviceId int, baseName string, baseDeviceId int) error {
+ devinfo, _ := getInfo(baseName)
+ doSuspend := devinfo != nil && devinfo.Exists != 0
+
+ if doSuspend {
+ if err := suspendDevice(baseName); err != nil {
+ return err
+ }
+ }
+
+ task, err := createTask(DeviceTargetMsg, poolName)
+ if task == nil {
+ if doSuspend {
+ resumeDevice(baseName)
+ }
+ return err
+ }
+
+ if err := task.SetSector(0); err != nil {
+ if doSuspend {
+ resumeDevice(baseName)
+ }
+ return fmt.Errorf("Can't set sector")
+ }
+
+ if err := task.SetMessage(fmt.Sprintf("create_snap %d %d", deviceId, baseDeviceId)); err != nil {
+ if doSuspend {
+ resumeDevice(baseName)
+ }
+ return fmt.Errorf("Can't set message")
+ }
+
+ if err := task.Run(); err != nil {
+ if doSuspend {
+ resumeDevice(baseName)
+ }
+ return fmt.Errorf("Error running DeviceCreate (createSnapDevice)")
+ }
+
+ if doSuspend {
+ if err := resumeDevice(baseName); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/runtime/graphdriver/devmapper/devmapper_doc.go b/runtime/graphdriver/devmapper/devmapper_doc.go
new file mode 100644
index 0000000000..c1c3e3891b
--- /dev/null
+++ b/runtime/graphdriver/devmapper/devmapper_doc.go
@@ -0,0 +1,106 @@
+package devmapper
+
+// Definition of struct dm_task and sub structures (from lvm2)
+//
+// struct dm_ioctl {
+// /*
+// * The version number is made up of three parts:
+// * major - no backward or forward compatibility,
+// * minor - only backwards compatible,
+// * patch - both backwards and forwards compatible.
+// *
+// * All clients of the ioctl interface should fill in the
+// * version number of the interface that they were
+// * compiled with.
+// *
+// * All recognised ioctl commands (ie. those that don't
+// * return -ENOTTY) fill out this field, even if the
+// * command failed.
+// */
+// uint32_t version[3]; /* in/out */
+// uint32_t data_size; /* total size of data passed in
+// * including this struct */
+
+// uint32_t data_start; /* offset to start of data
+// * relative to start of this struct */
+
+// uint32_t target_count; /* in/out */
+// int32_t open_count; /* out */
+// uint32_t flags; /* in/out */
+
+// /*
+// * event_nr holds either the event number (input and output) or the
+// * udev cookie value (input only).
+// * The DM_DEV_WAIT ioctl takes an event number as input.
+// * The DM_SUSPEND, DM_DEV_REMOVE and DM_DEV_RENAME ioctls
+// * use the field as a cookie to return in the DM_COOKIE
+// * variable with the uevents they issue.
+// * For output, the ioctls return the event number, not the cookie.
+// */
+// uint32_t event_nr; /* in/out */
+// uint32_t padding;
+
+// uint64_t dev; /* in/out */
+
+// char name[DM_NAME_LEN]; /* device name */
+// char uuid[DM_UUID_LEN]; /* unique identifier for
+// * the block device */
+// char data[7]; /* padding or data */
+// };
+
+// struct target {
+// uint64_t start;
+// uint64_t length;
+// char *type;
+// char *params;
+
+// struct target *next;
+// };
+
+// typedef enum {
+// DM_ADD_NODE_ON_RESUME, /* add /dev/mapper node with dmsetup resume */
+// DM_ADD_NODE_ON_CREATE /* add /dev/mapper node with dmsetup create */
+// } dm_add_node_t;
+
+// struct dm_task {
+// int type;
+// char *dev_name;
+// char *mangled_dev_name;
+
+// struct target *head, *tail;
+
+// int read_only;
+// uint32_t event_nr;
+// int major;
+// int minor;
+// int allow_default_major_fallback;
+// uid_t uid;
+// gid_t gid;
+// mode_t mode;
+// uint32_t read_ahead;
+// uint32_t read_ahead_flags;
+// union {
+// struct dm_ioctl *v4;
+// } dmi;
+// char *newname;
+// char *message;
+// char *geometry;
+// uint64_t sector;
+// int no_flush;
+// int no_open_count;
+// int skip_lockfs;
+// int query_inactive_table;
+// int suppress_identical_reload;
+// dm_add_node_t add_node;
+// uint64_t existing_table_size;
+// int cookie_set;
+// int new_uuid;
+// int secure_data;
+// int retry_remove;
+// int enable_checks;
+// int expected_errno;
+
+// char *uuid;
+// char *mangled_uuid;
+// };
+//
diff --git a/runtime/graphdriver/devmapper/devmapper_log.go b/runtime/graphdriver/devmapper/devmapper_log.go
new file mode 100644
index 0000000000..18dde7cca5
--- /dev/null
+++ b/runtime/graphdriver/devmapper/devmapper_log.go
@@ -0,0 +1,15 @@
+// +build linux,amd64
+
+package devmapper
+
+import "C"
+
+// Due to the way cgo works this has to be in a separate file, as devmapper.go has
+// definitions in the cgo block, which is incompatible with using "//export"
+
+//export DevmapperLogCallback
+func DevmapperLogCallback(level C.int, file *C.char, line C.int, dm_errno_or_class C.int, message *C.char) {
+ if dmLogger != nil {
+ dmLogger.log(int(level), C.GoString(file), int(line), int(dm_errno_or_class), C.GoString(message))
+ }
+}
diff --git a/runtime/graphdriver/devmapper/devmapper_test.go b/runtime/graphdriver/devmapper/devmapper_test.go
new file mode 100644
index 0000000000..3ffa163ceb
--- /dev/null
+++ b/runtime/graphdriver/devmapper/devmapper_test.go
@@ -0,0 +1,287 @@
+// +build linux,amd64
+
+package devmapper
+
+import (
+ "testing"
+)
+
+func TestTaskCreate(t *testing.T) {
+ t.Skip("FIXME: not a unit test")
+ // Test success
+ taskCreate(t, DeviceInfo)
+
+ // Test Failure
+ DmTaskCreate = dmTaskCreateFail
+ defer func() { DmTaskCreate = dmTaskCreateFct }()
+ if task := TaskCreate(-1); task != nil {
+ t.Fatalf("An error should have occured while creating an invalid task.")
+ }
+}
+
+func TestTaskRun(t *testing.T) {
+ t.Skip("FIXME: not a unit test")
+ task := taskCreate(t, DeviceInfo)
+
+ // Test success
+ // Perform the RUN
+ if err := task.Run(); err != nil {
+ t.Fatal(err)
+ }
+ // Make sure we don't have error with GetInfo
+ if _, err := task.GetInfo(); err != nil {
+ t.Fatal(err)
+ }
+
+ // Test failure
+ DmTaskRun = dmTaskRunFail
+ defer func() { DmTaskRun = dmTaskRunFct }()
+
+ task = taskCreate(t, DeviceInfo)
+ // Perform the RUN
+ if err := task.Run(); err != ErrTaskRun {
+ t.Fatalf("An error should have occured while running task.")
+ }
+ // Make sure GetInfo also fails
+ if _, err := task.GetInfo(); err != ErrTaskGetInfo {
+ t.Fatalf("GetInfo should fail if task.Run() failed.")
+ }
+}
+
+func TestTaskSetName(t *testing.T) {
+ t.Skip("FIXME: not a unit test")
+ task := taskCreate(t, DeviceInfo)
+
+ // Test success
+ if err := task.SetName("test"); err != nil {
+ t.Fatal(err)
+ }
+
+ // Test failure
+ DmTaskSetName = dmTaskSetNameFail
+ defer func() { DmTaskSetName = dmTaskSetNameFct }()
+
+ if err := task.SetName("test"); err != ErrTaskSetName {
+ t.Fatalf("An error should have occured while runnign SetName.")
+ }
+}
+
+func TestTaskSetMessage(t *testing.T) {
+ t.Skip("FIXME: not a unit test")
+ task := taskCreate(t, DeviceInfo)
+
+ // Test success
+ if err := task.SetMessage("test"); err != nil {
+ t.Fatal(err)
+ }
+
+ // Test failure
+ DmTaskSetMessage = dmTaskSetMessageFail
+ defer func() { DmTaskSetMessage = dmTaskSetMessageFct }()
+
+ if err := task.SetMessage("test"); err != ErrTaskSetMessage {
+ t.Fatalf("An error should have occured while runnign SetMessage.")
+ }
+}
+
+func TestTaskSetSector(t *testing.T) {
+ t.Skip("FIXME: not a unit test")
+ task := taskCreate(t, DeviceInfo)
+
+ // Test success
+ if err := task.SetSector(128); err != nil {
+ t.Fatal(err)
+ }
+
+ DmTaskSetSector = dmTaskSetSectorFail
+ defer func() { DmTaskSetSector = dmTaskSetSectorFct }()
+
+ // Test failure
+ if err := task.SetSector(0); err != ErrTaskSetSector {
+ t.Fatalf("An error should have occured while running SetSector.")
+ }
+}
+
+func TestTaskSetCookie(t *testing.T) {
+ t.Skip("FIXME: not a unit test")
+ var (
+ cookie uint = 0
+ task = taskCreate(t, DeviceInfo)
+ )
+
+ // Test success
+ if err := task.SetCookie(&cookie, 0); err != nil {
+ t.Fatal(err)
+ }
+
+ // Test failure
+ if err := task.SetCookie(nil, 0); err != ErrNilCookie {
+ t.Fatalf("An error should have occured while running SetCookie with nil cookie.")
+ }
+
+ DmTaskSetCookie = dmTaskSetCookieFail
+ defer func() { DmTaskSetCookie = dmTaskSetCookieFct }()
+
+ if err := task.SetCookie(&cookie, 0); err != ErrTaskSetCookie {
+ t.Fatalf("An error should have occured while running SetCookie.")
+ }
+}
+
+func TestTaskSetAddNode(t *testing.T) {
+ t.Skip("FIXME: not a unit test")
+ task := taskCreate(t, DeviceInfo)
+
+ // Test success
+ if err := task.SetAddNode(0); err != nil {
+ t.Fatal(err)
+ }
+
+ // Test failure
+ if err := task.SetAddNode(-1); err != ErrInvalidAddNode {
+ t.Fatalf("An error should have occured running SetAddNode with wrong node.")
+ }
+
+ DmTaskSetAddNode = dmTaskSetAddNodeFail
+ defer func() { DmTaskSetAddNode = dmTaskSetAddNodeFct }()
+
+ if err := task.SetAddNode(0); err != ErrTaskSetAddNode {
+ t.Fatalf("An error should have occured running SetAddNode.")
+ }
+}
+
+func TestTaskSetRo(t *testing.T) {
+ t.Skip("FIXME: not a unit test")
+ task := taskCreate(t, DeviceInfo)
+
+ // Test success
+ if err := task.SetRo(); err != nil {
+ t.Fatal(err)
+ }
+
+ // Test failure
+ DmTaskSetRo = dmTaskSetRoFail
+ defer func() { DmTaskSetRo = dmTaskSetRoFct }()
+
+ if err := task.SetRo(); err != ErrTaskSetRo {
+ t.Fatalf("An error should have occured running SetRo.")
+ }
+}
+
+func TestTaskAddTarget(t *testing.T) {
+ t.Skip("FIXME: not a unit test")
+ task := taskCreate(t, DeviceInfo)
+
+ // Test success
+ if err := task.AddTarget(0, 128, "thinp", ""); err != nil {
+ t.Fatal(err)
+ }
+
+ // Test failure
+ DmTaskAddTarget = dmTaskAddTargetFail
+ defer func() { DmTaskAddTarget = dmTaskAddTargetFct }()
+
+ if err := task.AddTarget(0, 128, "thinp", ""); err != ErrTaskAddTarget {
+ t.Fatalf("An error should have occured running AddTarget.")
+ }
+}
+
+// func TestTaskGetInfo(t *testing.T) {
+// task := taskCreate(t, DeviceInfo)
+
+// // Test success
+// if _, err := task.GetInfo(); err != nil {
+// t.Fatal(err)
+// }
+
+// // Test failure
+// DmTaskGetInfo = dmTaskGetInfoFail
+// defer func() { DmTaskGetInfo = dmTaskGetInfoFct }()
+
+// if _, err := task.GetInfo(); err != ErrTaskGetInfo {
+// t.Fatalf("An error should have occured running GetInfo.")
+// }
+// }
+
+// func TestTaskGetNextTarget(t *testing.T) {
+// task := taskCreate(t, DeviceInfo)
+
+// if next, _, _, _, _ := task.GetNextTarget(0); next == 0 {
+// t.Fatalf("The next target should not be 0.")
+// }
+// }
+
+/// Utils
+func taskCreate(t *testing.T, taskType TaskType) *Task {
+ task := TaskCreate(taskType)
+ if task == nil {
+ t.Fatalf("Error creating task")
+ }
+ return task
+}
+
+/// Failure function replacement
+func dmTaskCreateFail(t int) *CDmTask {
+ return nil
+}
+
+func dmTaskRunFail(task *CDmTask) int {
+ return -1
+}
+
+func dmTaskSetNameFail(task *CDmTask, name string) int {
+ return -1
+}
+
+func dmTaskSetMessageFail(task *CDmTask, message string) int {
+ return -1
+}
+
+func dmTaskSetSectorFail(task *CDmTask, sector uint64) int {
+ return -1
+}
+
+func dmTaskSetCookieFail(task *CDmTask, cookie *uint, flags uint16) int {
+ return -1
+}
+
+func dmTaskSetAddNodeFail(task *CDmTask, addNode AddNodeType) int {
+ return -1
+}
+
+func dmTaskSetRoFail(task *CDmTask) int {
+ return -1
+}
+
+func dmTaskAddTargetFail(task *CDmTask,
+ start, size uint64, ttype, params string) int {
+ return -1
+}
+
+func dmTaskGetInfoFail(task *CDmTask, info *Info) int {
+ return -1
+}
+
+func dmGetNextTargetFail(task *CDmTask, next uintptr, start, length *uint64,
+ target, params *string) uintptr {
+ return 0
+}
+
+func dmAttachLoopDeviceFail(filename string, fd *int) string {
+ return ""
+}
+
+func sysGetBlockSizeFail(fd uintptr, size *uint64) sysErrno {
+ return 1
+}
+
+func dmUdevWaitFail(cookie uint) int {
+ return -1
+}
+
+func dmSetDevDirFail(dir string) int {
+ return -1
+}
+
+func dmGetLibraryVersionFail(version *string) int {
+ return -1
+}
diff --git a/runtime/graphdriver/devmapper/devmapper_wrapper.go b/runtime/graphdriver/devmapper/devmapper_wrapper.go
new file mode 100644
index 0000000000..bf558affc8
--- /dev/null
+++ b/runtime/graphdriver/devmapper/devmapper_wrapper.go
@@ -0,0 +1,229 @@
+// +build linux,amd64
+
+package devmapper
+
+/*
+#cgo LDFLAGS: -L. -ldevmapper
+#include <libdevmapper.h>
+#include <linux/loop.h> // FIXME: present only for defines, maybe we can remove it?
+#include <linux/fs.h> // FIXME: present only for BLKGETSIZE64, maybe we can remove it?
+
+#ifndef LOOP_CTL_GET_FREE
+ #define LOOP_CTL_GET_FREE 0x4C82
+#endif
+
+#ifndef LO_FLAGS_PARTSCAN
+ #define LO_FLAGS_PARTSCAN 8
+#endif
+
+// FIXME: Can't we find a way to do the logging in pure Go?
+extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str);
+
+static void log_cb(int level, const char *file, int line, int dm_errno_or_class, const char *f, ...)
+{
+ char buffer[256];
+ va_list ap;
+
+ va_start(ap, f);
+ vsnprintf(buffer, 256, f, ap);
+ va_end(ap);
+
+ DevmapperLogCallback(level, (char *)file, line, dm_errno_or_class, buffer);
+}
+
+static void log_with_errno_init()
+{
+ dm_log_with_errno_init(log_cb);
+}
+*/
+import "C"
+
+import (
+ "unsafe"
+)
+
+type (
+ CDmTask C.struct_dm_task
+
+ CLoopInfo64 C.struct_loop_info64
+ LoopInfo64 struct {
+ loDevice uint64 /* ioctl r/o */
+ loInode uint64 /* ioctl r/o */
+ loRdevice uint64 /* ioctl r/o */
+ loOffset uint64
+ loSizelimit uint64 /* bytes, 0 == max available */
+ loNumber uint32 /* ioctl r/o */
+ loEncrypt_type uint32
+ loEncrypt_key_size uint32 /* ioctl w/o */
+ loFlags uint32 /* ioctl r/o */
+ loFileName [LoNameSize]uint8
+ loCryptName [LoNameSize]uint8
+ loEncryptKey [LoKeySize]uint8 /* ioctl w/o */
+ loInit [2]uint64
+ }
+)
+
+// IOCTL consts
+const (
+ BlkGetSize64 = C.BLKGETSIZE64
+ BlkDiscard = C.BLKDISCARD
+
+ LoopSetFd = C.LOOP_SET_FD
+ LoopCtlGetFree = C.LOOP_CTL_GET_FREE
+ LoopGetStatus64 = C.LOOP_GET_STATUS64
+ LoopSetStatus64 = C.LOOP_SET_STATUS64
+ LoopClrFd = C.LOOP_CLR_FD
+ LoopSetCapacity = C.LOOP_SET_CAPACITY
+)
+
+const (
+ LoFlagsAutoClear = C.LO_FLAGS_AUTOCLEAR
+ LoFlagsReadOnly = C.LO_FLAGS_READ_ONLY
+ LoFlagsPartScan = C.LO_FLAGS_PARTSCAN
+ LoKeySize = C.LO_KEY_SIZE
+ LoNameSize = C.LO_NAME_SIZE
+)
+
+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
+)
+
+func free(p *C.char) {
+ C.free(unsafe.Pointer(p))
+}
+
+func dmTaskDestroyFct(task *CDmTask) {
+ C.dm_task_destroy((*C.struct_dm_task)(task))
+}
+
+func dmTaskCreateFct(taskType int) *CDmTask {
+ return (*CDmTask)(C.dm_task_create(C.int(taskType)))
+}
+
+func dmTaskRunFct(task *CDmTask) int {
+ ret, _ := C.dm_task_run((*C.struct_dm_task)(task))
+ return int(ret)
+}
+
+func dmTaskSetNameFct(task *CDmTask, name string) int {
+ Cname := C.CString(name)
+ defer free(Cname)
+
+ return int(C.dm_task_set_name((*C.struct_dm_task)(task), Cname))
+}
+
+func dmTaskSetMessageFct(task *CDmTask, message string) int {
+ Cmessage := C.CString(message)
+ defer free(Cmessage)
+
+ return int(C.dm_task_set_message((*C.struct_dm_task)(task), Cmessage))
+}
+
+func dmTaskSetSectorFct(task *CDmTask, sector uint64) int {
+ return int(C.dm_task_set_sector((*C.struct_dm_task)(task), C.uint64_t(sector)))
+}
+
+func dmTaskSetCookieFct(task *CDmTask, cookie *uint, flags uint16) int {
+ cCookie := C.uint32_t(*cookie)
+ defer func() {
+ *cookie = uint(cCookie)
+ }()
+ return int(C.dm_task_set_cookie((*C.struct_dm_task)(task), &cCookie, C.uint16_t(flags)))
+}
+
+func dmTaskSetAddNodeFct(task *CDmTask, addNode AddNodeType) int {
+ return int(C.dm_task_set_add_node((*C.struct_dm_task)(task), C.dm_add_node_t(addNode)))
+}
+
+func dmTaskSetRoFct(task *CDmTask) int {
+ return int(C.dm_task_set_ro((*C.struct_dm_task)(task)))
+}
+
+func dmTaskAddTargetFct(task *CDmTask,
+ start, size uint64, ttype, params string) int {
+
+ Cttype := C.CString(ttype)
+ defer free(Cttype)
+
+ Cparams := C.CString(params)
+ defer free(Cparams)
+
+ return int(C.dm_task_add_target((*C.struct_dm_task)(task), C.uint64_t(start), C.uint64_t(size), Cttype, Cparams))
+}
+
+func dmTaskGetInfoFct(task *CDmTask, info *Info) int {
+ Cinfo := C.struct_dm_info{}
+ defer func() {
+ info.Exists = int(Cinfo.exists)
+ info.Suspended = int(Cinfo.suspended)
+ info.LiveTable = int(Cinfo.live_table)
+ info.InactiveTable = int(Cinfo.inactive_table)
+ info.OpenCount = int32(Cinfo.open_count)
+ info.EventNr = uint32(Cinfo.event_nr)
+ info.Major = uint32(Cinfo.major)
+ info.Minor = uint32(Cinfo.minor)
+ info.ReadOnly = int(Cinfo.read_only)
+ info.TargetCount = int32(Cinfo.target_count)
+ }()
+ return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo))
+}
+
+func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
+ var (
+ Cstart, Clength C.uint64_t
+ CtargetType, Cparams *C.char
+ )
+ defer func() {
+ *start = uint64(Cstart)
+ *length = uint64(Clength)
+ *target = C.GoString(CtargetType)
+ *params = C.GoString(Cparams)
+ }()
+
+ nextp := C.dm_get_next_target((*C.struct_dm_task)(task), unsafe.Pointer(next), &Cstart, &Clength, &CtargetType, &Cparams)
+ return uintptr(nextp)
+}
+
+func dmUdevWaitFct(cookie uint) int {
+ return int(C.dm_udev_wait(C.uint32_t(cookie)))
+}
+
+func dmLogInitVerboseFct(level int) {
+ C.dm_log_init_verbose(C.int(level))
+}
+
+func logWithErrnoInitFct() {
+ C.log_with_errno_init()
+}
+
+func dmSetDevDirFct(dir string) int {
+ Cdir := C.CString(dir)
+ defer free(Cdir)
+
+ return int(C.dm_set_dev_dir(Cdir))
+}
+
+func dmGetLibraryVersionFct(version *string) int {
+ buffer := C.CString(string(make([]byte, 128)))
+ defer free(buffer)
+ defer func() {
+ *version = C.GoString(buffer)
+ }()
+ return int(C.dm_get_library_version(buffer, 128))
+}
diff --git a/runtime/graphdriver/devmapper/driver.go b/runtime/graphdriver/devmapper/driver.go
new file mode 100644
index 0000000000..35fe883f26
--- /dev/null
+++ b/runtime/graphdriver/devmapper/driver.go
@@ -0,0 +1,142 @@
+// +build linux,amd64
+
+package devmapper
+
+import (
+ "fmt"
+ "github.com/dotcloud/docker/runtime/graphdriver"
+ "github.com/dotcloud/docker/utils"
+ "io/ioutil"
+ "os"
+ "path"
+)
+
+func init() {
+ graphdriver.Register("devicemapper", Init)
+}
+
+// Placeholder interfaces, to be replaced
+// at integration.
+
+// End of placeholder interfaces.
+
+type Driver struct {
+ *DeviceSet
+ home string
+}
+
+var Init = func(home string) (graphdriver.Driver, error) {
+ deviceSet, err := NewDeviceSet(home, true)
+ if err != nil {
+ return nil, err
+ }
+ d := &Driver{
+ DeviceSet: deviceSet,
+ home: home,
+ }
+ return d, nil
+}
+
+func (d *Driver) String() string {
+ return "devicemapper"
+}
+
+func (d *Driver) Status() [][2]string {
+ s := d.DeviceSet.Status()
+
+ status := [][2]string{
+ {"Pool Name", s.PoolName},
+ {"Data file", s.DataLoopback},
+ {"Metadata file", s.MetadataLoopback},
+ {"Data Space Used", fmt.Sprintf("%.1f Mb", float64(s.Data.Used)/(1024*1024))},
+ {"Data Space Total", fmt.Sprintf("%.1f Mb", float64(s.Data.Total)/(1024*1024))},
+ {"Metadata Space Used", fmt.Sprintf("%.1f Mb", float64(s.Metadata.Used)/(1024*1024))},
+ {"Metadata Space Total", fmt.Sprintf("%.1f Mb", float64(s.Metadata.Total)/(1024*1024))},
+ }
+ return status
+}
+
+func (d *Driver) Cleanup() error {
+ return d.DeviceSet.Shutdown()
+}
+
+func (d *Driver) Create(id, parent string, mountLabel string) error {
+ if err := d.DeviceSet.AddDevice(id, parent); err != nil {
+ return err
+ }
+ mp := path.Join(d.home, "mnt", id)
+ if err := d.mount(id, mp); err != nil {
+ return err
+ }
+
+ if err := osMkdirAll(path.Join(mp, "rootfs"), 0755); err != nil && !osIsExist(err) {
+ return err
+ }
+
+ // Create an "id" file with the container/image id in it to help reconscruct this in case
+ // of later problems
+ if err := ioutil.WriteFile(path.Join(mp, "id"), []byte(id), 0600); err != nil {
+ return err
+ }
+
+ // We float this reference so that the next Get call can
+ // steal it, so we don't have to unmount
+ if err := d.DeviceSet.UnmountDevice(id, UnmountFloat); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (d *Driver) Remove(id string) error {
+ if !d.DeviceSet.HasDevice(id) {
+ // Consider removing a non-existing device a no-op
+ // This is useful to be able to progress on container removal
+ // if the underlying device has gone away due to earlier errors
+ return nil
+ }
+
+ // Sink the float from create in case no Get() call was made
+ if err := d.DeviceSet.UnmountDevice(id, UnmountSink); err != nil {
+ return err
+ }
+ // This assumes the device has been properly Get/Put:ed and thus is unmounted
+ if err := d.DeviceSet.DeleteDevice(id); err != nil {
+ return err
+ }
+
+ mp := path.Join(d.home, "mnt", id)
+ if err := os.RemoveAll(mp); err != nil && !os.IsNotExist(err) {
+ return err
+ }
+
+ return nil
+}
+
+func (d *Driver) Get(id string) (string, error) {
+ mp := path.Join(d.home, "mnt", id)
+ if err := d.mount(id, mp); err != nil {
+ return "", err
+ }
+
+ return path.Join(mp, "rootfs"), nil
+}
+
+func (d *Driver) Put(id string) {
+ if err := d.DeviceSet.UnmountDevice(id, UnmountRegular); err != nil {
+ utils.Errorf("Warning: error unmounting device %s: %s\n", id, err)
+ }
+}
+
+func (d *Driver) mount(id, mountPoint string) error {
+ // Create the target directories if they don't exist
+ if err := osMkdirAll(mountPoint, 0755); err != nil && !osIsExist(err) {
+ return err
+ }
+ // Mount the device
+ return d.DeviceSet.MountDevice(id, mountPoint, "")
+}
+
+func (d *Driver) Exists(id string) bool {
+ return d.Devices[id] != nil
+}
diff --git a/runtime/graphdriver/devmapper/driver_test.go b/runtime/graphdriver/devmapper/driver_test.go
new file mode 100644
index 0000000000..4ca72db0ca
--- /dev/null
+++ b/runtime/graphdriver/devmapper/driver_test.go
@@ -0,0 +1,886 @@
+// +build linux,amd64
+
+package devmapper
+
+import (
+ "fmt"
+ "github.com/dotcloud/docker/runtime/graphdriver"
+ "io/ioutil"
+ "path"
+ "runtime"
+ "strings"
+ "syscall"
+ "testing"
+)
+
+func init() {
+ // Reduce the size the the base fs and loopback for the tests
+ DefaultDataLoopbackSize = 300 * 1024 * 1024
+ DefaultMetaDataLoopbackSize = 200 * 1024 * 1024
+ DefaultBaseFsSize = 300 * 1024 * 1024
+}
+
+// denyAllDevmapper mocks all calls to libdevmapper in the unit tests, and denies them by default
+func denyAllDevmapper() {
+ // Hijack all calls to libdevmapper with default panics.
+ // Authorized calls are selectively hijacked in each tests.
+ DmTaskCreate = func(t int) *CDmTask {
+ panic("DmTaskCreate: this method should not be called here")
+ }
+ DmTaskRun = func(task *CDmTask) int {
+ panic("DmTaskRun: this method should not be called here")
+ }
+ DmTaskSetName = func(task *CDmTask, name string) int {
+ panic("DmTaskSetName: this method should not be called here")
+ }
+ DmTaskSetMessage = func(task *CDmTask, message string) int {
+ panic("DmTaskSetMessage: this method should not be called here")
+ }
+ DmTaskSetSector = func(task *CDmTask, sector uint64) int {
+ panic("DmTaskSetSector: this method should not be called here")
+ }
+ DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int {
+ panic("DmTaskSetCookie: this method should not be called here")
+ }
+ DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int {
+ panic("DmTaskSetAddNode: this method should not be called here")
+ }
+ DmTaskSetRo = func(task *CDmTask) int {
+ panic("DmTaskSetRo: this method should not be called here")
+ }
+ DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
+ panic("DmTaskAddTarget: this method should not be called here")
+ }
+ DmTaskGetInfo = func(task *CDmTask, info *Info) int {
+ panic("DmTaskGetInfo: this method should not be called here")
+ }
+ DmGetNextTarget = func(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
+ panic("DmGetNextTarget: this method should not be called here")
+ }
+ DmUdevWait = func(cookie uint) int {
+ panic("DmUdevWait: this method should not be called here")
+ }
+ DmSetDevDir = func(dir string) int {
+ panic("DmSetDevDir: this method should not be called here")
+ }
+ DmGetLibraryVersion = func(version *string) int {
+ panic("DmGetLibraryVersion: this method should not be called here")
+ }
+ DmLogInitVerbose = func(level int) {
+ panic("DmLogInitVerbose: this method should not be called here")
+ }
+ DmTaskDestroy = func(task *CDmTask) {
+ panic("DmTaskDestroy: this method should not be called here")
+ }
+ LogWithErrnoInit = func() {
+ panic("LogWithErrnoInit: this method should not be called here")
+ }
+}
+
+func denyAllSyscall() {
+ sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) {
+ panic("sysMount: this method should not be called here")
+ }
+ sysUnmount = func(target string, flags int) (err error) {
+ panic("sysUnmount: this method should not be called here")
+ }
+ sysCloseOnExec = func(fd int) {
+ panic("sysCloseOnExec: this method should not be called here")
+ }
+ sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
+ panic("sysSyscall: this method should not be called here")
+ }
+ // Not a syscall, but forbidding it here anyway
+ Mounted = func(mnt string) (bool, error) {
+ panic("devmapper.Mounted: this method should not be called here")
+ }
+ // osOpenFile = os.OpenFile
+ // osNewFile = os.NewFile
+ // osCreate = os.Create
+ // osStat = os.Stat
+ // osIsNotExist = os.IsNotExist
+ // osIsExist = os.IsExist
+ // osMkdirAll = os.MkdirAll
+ // osRemoveAll = os.RemoveAll
+ // osRename = os.Rename
+ // osReadlink = os.Readlink
+
+ // execRun = func(name string, args ...string) error {
+ // return exec.Command(name, args...).Run()
+ // }
+}
+
+func mkTestDirectory(t *testing.T) string {
+ dir, err := ioutil.TempDir("", "docker-test-devmapper-")
+ if err != nil {
+ t.Fatal(err)
+ }
+ return dir
+}
+
+func newDriver(t *testing.T) *Driver {
+ home := mkTestDirectory(t)
+ d, err := Init(home)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return d.(*Driver)
+}
+
+func cleanup(d *Driver) {
+ d.Cleanup()
+ osRemoveAll(d.home)
+}
+
+type Set map[string]bool
+
+func (r Set) Assert(t *testing.T, names ...string) {
+ for _, key := range names {
+ required := true
+ if strings.HasPrefix(key, "?") {
+ key = key[1:]
+ required = false
+ }
+ if _, exists := r[key]; !exists && required {
+ t.Fatalf("Key not set: %s", key)
+ }
+ delete(r, key)
+ }
+ if len(r) != 0 {
+ t.Fatalf("Unexpected keys: %v", r)
+ }
+}
+
+func TestInit(t *testing.T) {
+ var (
+ calls = make(Set)
+ taskMessages = make(Set)
+ taskTypes = make(Set)
+ home = mkTestDirectory(t)
+ )
+ defer osRemoveAll(home)
+
+ func() {
+ denyAllDevmapper()
+ DmSetDevDir = func(dir string) int {
+ calls["DmSetDevDir"] = true
+ expectedDir := "/dev"
+ if dir != expectedDir {
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmSetDevDir(%v)\nReceived: DmSetDevDir(%v)\n", expectedDir, dir)
+ }
+ return 0
+ }
+ LogWithErrnoInit = func() {
+ calls["DmLogWithErrnoInit"] = true
+ }
+ var task1 CDmTask
+ DmTaskCreate = func(taskType int) *CDmTask {
+ calls["DmTaskCreate"] = true
+ taskTypes[fmt.Sprintf("%d", taskType)] = true
+ return &task1
+ }
+ DmTaskSetName = func(task *CDmTask, name string) int {
+ calls["DmTaskSetName"] = true
+ expectedTask := &task1
+ if task != expectedTask {
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetName(%v)\nReceived: DmTaskSetName(%v)\n", expectedTask, task)
+ }
+ // FIXME: use Set.AssertRegexp()
+ if !strings.HasPrefix(name, "docker-") && !strings.HasPrefix(name, "/dev/mapper/docker-") ||
+ !strings.HasSuffix(name, "-pool") && !strings.HasSuffix(name, "-base") {
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetName(%v)\nReceived: DmTaskSetName(%v)\n", "docker-...-pool", name)
+ }
+ return 1
+ }
+ DmTaskRun = func(task *CDmTask) int {
+ calls["DmTaskRun"] = true
+ expectedTask := &task1
+ if task != expectedTask {
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskRun(%v)\nReceived: DmTaskRun(%v)\n", expectedTask, task)
+ }
+ return 1
+ }
+ DmTaskGetInfo = func(task *CDmTask, info *Info) int {
+ calls["DmTaskGetInfo"] = true
+ expectedTask := &task1
+ if task != expectedTask {
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskGetInfo(%v)\nReceived: DmTaskGetInfo(%v)\n", expectedTask, task)
+ }
+ // This will crash if info is not dereferenceable
+ info.Exists = 0
+ return 1
+ }
+ DmTaskSetSector = func(task *CDmTask, sector uint64) int {
+ calls["DmTaskSetSector"] = true
+ expectedTask := &task1
+ if task != expectedTask {
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetSector(%v)\nReceived: DmTaskSetSector(%v)\n", expectedTask, task)
+ }
+ if expectedSector := uint64(0); sector != expectedSector {
+ t.Fatalf("Wrong libdevmapper call to DmTaskSetSector\nExpected: %v\nReceived: %v\n", expectedSector, sector)
+ }
+ return 1
+ }
+ DmTaskSetMessage = func(task *CDmTask, message string) int {
+ calls["DmTaskSetMessage"] = true
+ expectedTask := &task1
+ if task != expectedTask {
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskSetSector(%v)\nReceived: DmTaskSetSector(%v)\n", expectedTask, task)
+ }
+ taskMessages[message] = true
+ return 1
+ }
+ DmTaskDestroy = func(task *CDmTask) {
+ calls["DmTaskDestroy"] = true
+ expectedTask := &task1
+ if task != expectedTask {
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task)
+ }
+ }
+ DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
+ calls["DmTaskSetTarget"] = true
+ expectedTask := &task1
+ if task != expectedTask {
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task)
+ }
+ if start != 0 {
+ t.Fatalf("Wrong start: %d != %d", start, 0)
+ }
+ if ttype != "thin" && ttype != "thin-pool" {
+ t.Fatalf("Wrong ttype: %s", ttype)
+ }
+ // Quick smoke test
+ if params == "" {
+ t.Fatalf("Params should not be empty")
+ }
+ return 1
+ }
+ fakeCookie := uint(4321)
+ DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int {
+ calls["DmTaskSetCookie"] = true
+ expectedTask := &task1
+ if task != expectedTask {
+ t.Fatalf("Wrong libdevmapper call\nExpected: DmTaskDestroy(%v)\nReceived: DmTaskDestroy(%v)\n", expectedTask, task)
+ }
+ if flags != 0 {
+ t.Fatalf("Cookie flags should be 0 (not %x)", flags)
+ }
+ *cookie = fakeCookie
+ return 1
+ }
+ DmUdevWait = func(cookie uint) int {
+ calls["DmUdevWait"] = true
+ if cookie != fakeCookie {
+ t.Fatalf("Wrong cookie: %d != %d", cookie, fakeCookie)
+ }
+ return 1
+ }
+ DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int {
+ if addNode != AddNodeOnCreate {
+ t.Fatalf("Wrong AddNoteType: %v (expected %v)", addNode, AddNodeOnCreate)
+ }
+ calls["DmTaskSetAddNode"] = true
+ return 1
+ }
+ execRun = func(name string, args ...string) error {
+ calls["execRun"] = true
+ if name != "mkfs.ext4" {
+ t.Fatalf("Expected %s to be executed, not %s", "mkfs.ext4", name)
+ }
+ return nil
+ }
+ driver, err := Init(home)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() {
+ if err := driver.Cleanup(); err != nil {
+ t.Fatal(err)
+ }
+ }()
+ }()
+ // Put all tests in a function to make sure the garbage collection will
+ // occur.
+
+ // Call GC to cleanup runtime.Finalizers
+ runtime.GC()
+
+ calls.Assert(t,
+ "DmSetDevDir",
+ "DmLogWithErrnoInit",
+ "DmTaskSetName",
+ "DmTaskRun",
+ "DmTaskGetInfo",
+ "DmTaskDestroy",
+ "execRun",
+ "DmTaskCreate",
+ "DmTaskSetTarget",
+ "DmTaskSetCookie",
+ "DmUdevWait",
+ "DmTaskSetSector",
+ "DmTaskSetMessage",
+ "DmTaskSetAddNode",
+ )
+ taskTypes.Assert(t, "0", "6", "17")
+ taskMessages.Assert(t, "create_thin 0", "set_transaction_id 0 1")
+}
+
+func fakeInit() func(home string) (graphdriver.Driver, error) {
+ oldInit := Init
+ Init = func(home string) (graphdriver.Driver, error) {
+ return &Driver{
+ home: home,
+ }, nil
+ }
+ return oldInit
+}
+
+func restoreInit(init func(home string) (graphdriver.Driver, error)) {
+ Init = init
+}
+
+func mockAllDevmapper(calls Set) {
+ DmSetDevDir = func(dir string) int {
+ calls["DmSetDevDir"] = true
+ return 0
+ }
+ LogWithErrnoInit = func() {
+ calls["DmLogWithErrnoInit"] = true
+ }
+ DmTaskCreate = func(taskType int) *CDmTask {
+ calls["DmTaskCreate"] = true
+ return &CDmTask{}
+ }
+ DmTaskSetName = func(task *CDmTask, name string) int {
+ calls["DmTaskSetName"] = true
+ return 1
+ }
+ DmTaskRun = func(task *CDmTask) int {
+ calls["DmTaskRun"] = true
+ return 1
+ }
+ DmTaskGetInfo = func(task *CDmTask, info *Info) int {
+ calls["DmTaskGetInfo"] = true
+ return 1
+ }
+ DmTaskSetSector = func(task *CDmTask, sector uint64) int {
+ calls["DmTaskSetSector"] = true
+ return 1
+ }
+ DmTaskSetMessage = func(task *CDmTask, message string) int {
+ calls["DmTaskSetMessage"] = true
+ return 1
+ }
+ DmTaskDestroy = func(task *CDmTask) {
+ calls["DmTaskDestroy"] = true
+ }
+ DmTaskAddTarget = func(task *CDmTask, start, size uint64, ttype, params string) int {
+ calls["DmTaskSetTarget"] = true
+ return 1
+ }
+ DmTaskSetCookie = func(task *CDmTask, cookie *uint, flags uint16) int {
+ calls["DmTaskSetCookie"] = true
+ return 1
+ }
+ DmUdevWait = func(cookie uint) int {
+ calls["DmUdevWait"] = true
+ return 1
+ }
+ DmTaskSetAddNode = func(task *CDmTask, addNode AddNodeType) int {
+ calls["DmTaskSetAddNode"] = true
+ return 1
+ }
+ execRun = func(name string, args ...string) error {
+ calls["execRun"] = true
+ return nil
+ }
+}
+
+func TestDriverName(t *testing.T) {
+ denyAllDevmapper()
+ defer denyAllDevmapper()
+
+ oldInit := fakeInit()
+ defer restoreInit(oldInit)
+
+ d := newDriver(t)
+ if d.String() != "devicemapper" {
+ t.Fatalf("Expected driver name to be devicemapper got %s", d.String())
+ }
+}
+
+func TestDriverCreate(t *testing.T) {
+ denyAllDevmapper()
+ denyAllSyscall()
+ defer denyAllSyscall()
+ defer denyAllDevmapper()
+
+ calls := make(Set)
+ mockAllDevmapper(calls)
+
+ sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) {
+ calls["sysMount"] = true
+ // FIXME: compare the exact source and target strings (inodes + devname)
+ if expectedSource := "/dev/mapper/docker-"; !strings.HasPrefix(source, expectedSource) {
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedSource, source)
+ }
+ if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) {
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target)
+ }
+ if expectedFstype := "ext4"; fstype != expectedFstype {
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFstype, fstype)
+ }
+ if expectedFlags := uintptr(3236757504); flags != expectedFlags {
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags)
+ }
+ return nil
+ }
+
+ Mounted = func(mnt string) (bool, error) {
+ calls["Mounted"] = true
+ if !strings.HasPrefix(mnt, "/tmp/docker-test-devmapper-") || !strings.HasSuffix(mnt, "/mnt/1") {
+ t.Fatalf("Wrong mounted call\nExpected: Mounted(%v)\nReceived: Mounted(%v)\n", "/tmp/docker-test-devmapper-.../mnt/1", mnt)
+ }
+ return false, nil
+ }
+
+ sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
+ calls["sysSyscall"] = true
+ if trap != sysSysIoctl {
+ t.Fatalf("Unexpected syscall. Expecting SYS_IOCTL, received: %d", trap)
+ }
+ switch a2 {
+ case LoopSetFd:
+ calls["ioctl.loopsetfd"] = true
+ case LoopCtlGetFree:
+ calls["ioctl.loopctlgetfree"] = true
+ case LoopGetStatus64:
+ calls["ioctl.loopgetstatus"] = true
+ case LoopSetStatus64:
+ calls["ioctl.loopsetstatus"] = true
+ case LoopClrFd:
+ calls["ioctl.loopclrfd"] = true
+ case LoopSetCapacity:
+ calls["ioctl.loopsetcapacity"] = true
+ case BlkGetSize64:
+ calls["ioctl.blkgetsize"] = true
+ default:
+ t.Fatalf("Unexpected IOCTL. Received %d", a2)
+ }
+ return 0, 0, 0
+ }
+
+ func() {
+ d := newDriver(t)
+
+ calls.Assert(t,
+ "DmSetDevDir",
+ "DmLogWithErrnoInit",
+ "DmTaskSetName",
+ "DmTaskRun",
+ "DmTaskGetInfo",
+ "execRun",
+ "DmTaskCreate",
+ "DmTaskSetTarget",
+ "DmTaskSetCookie",
+ "DmUdevWait",
+ "DmTaskSetSector",
+ "DmTaskSetMessage",
+ "DmTaskSetAddNode",
+ "sysSyscall",
+ "ioctl.blkgetsize",
+ "ioctl.loopsetfd",
+ "ioctl.loopsetstatus",
+ "?ioctl.loopctlgetfree",
+ )
+
+ if err := d.Create("1", "", ""); err != nil {
+ t.Fatal(err)
+ }
+ calls.Assert(t,
+ "DmTaskCreate",
+ "DmTaskGetInfo",
+ "sysMount",
+ "DmTaskRun",
+ "DmTaskSetTarget",
+ "DmTaskSetSector",
+ "DmTaskSetCookie",
+ "DmUdevWait",
+ "DmTaskSetName",
+ "DmTaskSetMessage",
+ "DmTaskSetAddNode",
+ )
+
+ }()
+
+ runtime.GC()
+
+ calls.Assert(t,
+ "DmTaskDestroy",
+ )
+}
+
+func TestDriverRemove(t *testing.T) {
+ denyAllDevmapper()
+ denyAllSyscall()
+ defer denyAllSyscall()
+ defer denyAllDevmapper()
+
+ calls := make(Set)
+ mockAllDevmapper(calls)
+
+ sysMount = func(source, target, fstype string, flags uintptr, data string) (err error) {
+ calls["sysMount"] = true
+ // FIXME: compare the exact source and target strings (inodes + devname)
+ if expectedSource := "/dev/mapper/docker-"; !strings.HasPrefix(source, expectedSource) {
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedSource, source)
+ }
+ if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) {
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target)
+ }
+ if expectedFstype := "ext4"; fstype != expectedFstype {
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFstype, fstype)
+ }
+ if expectedFlags := uintptr(3236757504); flags != expectedFlags {
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags)
+ }
+ return nil
+ }
+ sysUnmount = func(target string, flags int) (err error) {
+ calls["sysUnmount"] = true
+ // FIXME: compare the exact source and target strings (inodes + devname)
+ if expectedTarget := "/tmp/docker-test-devmapper-"; !strings.HasPrefix(target, expectedTarget) {
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedTarget, target)
+ }
+ if expectedFlags := 0; flags != expectedFlags {
+ t.Fatalf("Wrong syscall call\nExpected: Mount(%v)\nReceived: Mount(%v)\n", expectedFlags, flags)
+ }
+ return nil
+ }
+ Mounted = func(mnt string) (bool, error) {
+ calls["Mounted"] = true
+ return false, nil
+ }
+
+ sysSyscall = func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) {
+ calls["sysSyscall"] = true
+ if trap != sysSysIoctl {
+ t.Fatalf("Unexpected syscall. Expecting SYS_IOCTL, received: %d", trap)
+ }
+ switch a2 {
+ case LoopSetFd:
+ calls["ioctl.loopsetfd"] = true
+ case LoopCtlGetFree:
+ calls["ioctl.loopctlgetfree"] = true
+ case LoopGetStatus64:
+ calls["ioctl.loopgetstatus"] = true
+ case LoopSetStatus64:
+ calls["ioctl.loopsetstatus"] = true
+ case LoopClrFd:
+ calls["ioctl.loopclrfd"] = true
+ case LoopSetCapacity:
+ calls["ioctl.loopsetcapacity"] = true
+ case BlkGetSize64:
+ calls["ioctl.blkgetsize"] = true
+ default:
+ t.Fatalf("Unexpected IOCTL. Received %d", a2)
+ }
+ return 0, 0, 0
+ }
+
+ func() {
+ d := newDriver(t)
+
+ calls.Assert(t,
+ "DmSetDevDir",
+ "DmLogWithErrnoInit",
+ "DmTaskSetName",
+ "DmTaskRun",
+ "DmTaskGetInfo",
+ "execRun",
+ "DmTaskCreate",
+ "DmTaskSetTarget",
+ "DmTaskSetCookie",
+ "DmUdevWait",
+ "DmTaskSetSector",
+ "DmTaskSetMessage",
+ "DmTaskSetAddNode",
+ "sysSyscall",
+ "ioctl.blkgetsize",
+ "ioctl.loopsetfd",
+ "ioctl.loopsetstatus",
+ "?ioctl.loopctlgetfree",
+ )
+
+ if err := d.Create("1", "", ""); err != nil {
+ t.Fatal(err)
+ }
+
+ calls.Assert(t,
+ "DmTaskCreate",
+ "DmTaskGetInfo",
+ "sysMount",
+ "DmTaskRun",
+ "DmTaskSetTarget",
+ "DmTaskSetSector",
+ "DmTaskSetCookie",
+ "DmUdevWait",
+ "DmTaskSetName",
+ "DmTaskSetMessage",
+ "DmTaskSetAddNode",
+ )
+
+ Mounted = func(mnt string) (bool, error) {
+ calls["Mounted"] = true
+ return true, nil
+ }
+
+ if err := d.Remove("1"); err != nil {
+ t.Fatal(err)
+ }
+
+ calls.Assert(t,
+ "DmTaskRun",
+ "DmTaskSetSector",
+ "DmTaskSetName",
+ "DmTaskSetMessage",
+ "DmTaskCreate",
+ "DmTaskGetInfo",
+ "DmTaskSetCookie",
+ "DmTaskSetTarget",
+ "DmTaskSetAddNode",
+ "DmUdevWait",
+ "sysUnmount",
+ )
+ }()
+ runtime.GC()
+
+ calls.Assert(t,
+ "DmTaskDestroy",
+ )
+}
+
+func TestCleanup(t *testing.T) {
+ t.Skip("FIXME: not a unit test")
+ t.Skip("Unimplemented")
+ d := newDriver(t)
+ defer osRemoveAll(d.home)
+
+ mountPoints := make([]string, 2)
+
+ if err := d.Create("1", "", ""); err != nil {
+ t.Fatal(err)
+ }
+ // Mount the id
+ p, err := d.Get("1")
+ if err != nil {
+ t.Fatal(err)
+ }
+ mountPoints[0] = p
+
+ if err := d.Create("2", "1", ""); err != nil {
+ t.Fatal(err)
+ }
+
+ p, err = d.Get("2")
+ if err != nil {
+ t.Fatal(err)
+ }
+ mountPoints[1] = p
+
+ // Ensure that all the mount points are currently mounted
+ for _, p := range mountPoints {
+ if mounted, err := Mounted(p); err != nil {
+ t.Fatal(err)
+ } else if !mounted {
+ t.Fatalf("Expected %s to be mounted", p)
+ }
+ }
+
+ // Ensure that devices are active
+ for _, p := range []string{"1", "2"} {
+ if !d.HasActivatedDevice(p) {
+ t.Fatalf("Expected %s to have an active device", p)
+ }
+ }
+
+ if err := d.Cleanup(); err != nil {
+ t.Fatal(err)
+ }
+
+ // Ensure that all the mount points are no longer mounted
+ for _, p := range mountPoints {
+ if mounted, err := Mounted(p); err != nil {
+ t.Fatal(err)
+ } else if mounted {
+ t.Fatalf("Expected %s to not be mounted", p)
+ }
+ }
+
+ // Ensure that devices are no longer activated
+ for _, p := range []string{"1", "2"} {
+ if d.HasActivatedDevice(p) {
+ t.Fatalf("Expected %s not be an active device", p)
+ }
+ }
+}
+
+func TestNotMounted(t *testing.T) {
+ t.Skip("FIXME: not a unit test")
+ t.Skip("Not implemented")
+ d := newDriver(t)
+ defer cleanup(d)
+
+ if err := d.Create("1", "", ""); err != nil {
+ t.Fatal(err)
+ }
+
+ mounted, err := Mounted(path.Join(d.home, "mnt", "1"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if mounted {
+ t.Fatal("Id 1 should not be mounted")
+ }
+}
+
+func TestMounted(t *testing.T) {
+ t.Skip("FIXME: not a unit test")
+ d := newDriver(t)
+ defer cleanup(d)
+
+ if err := d.Create("1", "", ""); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := d.Get("1"); err != nil {
+ t.Fatal(err)
+ }
+
+ mounted, err := Mounted(path.Join(d.home, "mnt", "1"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !mounted {
+ t.Fatal("Id 1 should be mounted")
+ }
+}
+
+func TestInitCleanedDriver(t *testing.T) {
+ t.Skip("FIXME: not a unit test")
+ d := newDriver(t)
+
+ if err := d.Create("1", "", ""); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := d.Get("1"); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := d.Cleanup(); err != nil {
+ t.Fatal(err)
+ }
+
+ driver, err := Init(d.home)
+ if err != nil {
+ t.Fatal(err)
+ }
+ d = driver.(*Driver)
+ defer cleanup(d)
+
+ if _, err := d.Get("1"); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestMountMountedDriver(t *testing.T) {
+ t.Skip("FIXME: not a unit test")
+ d := newDriver(t)
+ defer cleanup(d)
+
+ if err := d.Create("1", "", ""); err != nil {
+ t.Fatal(err)
+ }
+
+ // Perform get on same id to ensure that it will
+ // not be mounted twice
+ if _, err := d.Get("1"); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := d.Get("1"); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestGetReturnsValidDevice(t *testing.T) {
+ t.Skip("FIXME: not a unit test")
+ d := newDriver(t)
+ defer cleanup(d)
+
+ if err := d.Create("1", "", ""); err != nil {
+ t.Fatal(err)
+ }
+
+ if !d.HasDevice("1") {
+ t.Fatalf("Expected id 1 to be in device set")
+ }
+
+ if _, err := d.Get("1"); err != nil {
+ t.Fatal(err)
+ }
+
+ if !d.HasActivatedDevice("1") {
+ t.Fatalf("Expected id 1 to be activated")
+ }
+
+ if !d.HasInitializedDevice("1") {
+ t.Fatalf("Expected id 1 to be initialized")
+ }
+}
+
+func TestDriverGetSize(t *testing.T) {
+ t.Skip("FIXME: not a unit test")
+ t.Skipf("Size is currently not implemented")
+
+ d := newDriver(t)
+ defer cleanup(d)
+
+ if err := d.Create("1", "", ""); err != nil {
+ t.Fatal(err)
+ }
+
+ mountPoint, err := d.Get("1")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ size := int64(1024)
+
+ f, err := osCreate(path.Join(mountPoint, "test_file"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := f.Truncate(size); err != nil {
+ t.Fatal(err)
+ }
+ f.Close()
+
+ // diffSize, err := d.DiffSize("1")
+ // if err != nil {
+ // t.Fatal(err)
+ // }
+ // if diffSize != size {
+ // t.Fatalf("Expected size %d got %d", size, diffSize)
+ // }
+}
+
+func assertMap(t *testing.T, m map[string]bool, keys ...string) {
+ for _, key := range keys {
+ if _, exists := m[key]; !exists {
+ t.Fatalf("Key not set: %s", key)
+ }
+ delete(m, key)
+ }
+ if len(m) != 0 {
+ t.Fatalf("Unexpected keys: %v", m)
+ }
+}
diff --git a/runtime/graphdriver/devmapper/ioctl.go b/runtime/graphdriver/devmapper/ioctl.go
new file mode 100644
index 0000000000..30bafff943
--- /dev/null
+++ b/runtime/graphdriver/devmapper/ioctl.go
@@ -0,0 +1,71 @@
+// +build linux,amd64
+
+package devmapper
+
+import (
+ "unsafe"
+)
+
+func ioctlLoopCtlGetFree(fd uintptr) (int, error) {
+ index, _, err := sysSyscall(sysSysIoctl, fd, LoopCtlGetFree, 0)
+ if err != 0 {
+ return 0, err
+ }
+ return int(index), nil
+}
+
+func ioctlLoopSetFd(loopFd, sparseFd uintptr) error {
+ if _, _, err := sysSyscall(sysSysIoctl, loopFd, LoopSetFd, sparseFd); err != 0 {
+ return err
+ }
+ return nil
+}
+
+func ioctlLoopSetStatus64(loopFd uintptr, loopInfo *LoopInfo64) error {
+ if _, _, err := sysSyscall(sysSysIoctl, loopFd, LoopSetStatus64, uintptr(unsafe.Pointer(loopInfo))); err != 0 {
+ return err
+ }
+ return nil
+}
+
+func ioctlLoopClrFd(loopFd uintptr) error {
+ if _, _, err := sysSyscall(sysSysIoctl, loopFd, LoopClrFd, 0); err != 0 {
+ return err
+ }
+ return nil
+}
+
+func ioctlLoopGetStatus64(loopFd uintptr) (*LoopInfo64, error) {
+ loopInfo := &LoopInfo64{}
+
+ if _, _, err := sysSyscall(sysSysIoctl, loopFd, LoopGetStatus64, uintptr(unsafe.Pointer(loopInfo))); err != 0 {
+ return nil, err
+ }
+ return loopInfo, nil
+}
+
+func ioctlLoopSetCapacity(loopFd uintptr, value int) error {
+ if _, _, err := sysSyscall(sysSysIoctl, loopFd, LoopSetCapacity, uintptr(value)); err != 0 {
+ return err
+ }
+ return nil
+}
+
+func ioctlBlkGetSize64(fd uintptr) (int64, error) {
+ var size int64
+ if _, _, err := sysSyscall(sysSysIoctl, fd, BlkGetSize64, uintptr(unsafe.Pointer(&size))); err != 0 {
+ return 0, err
+ }
+ return size, nil
+}
+
+func ioctlBlkDiscard(fd uintptr, offset, length uint64) error {
+ var r [2]uint64
+ r[0] = offset
+ r[1] = length
+
+ if _, _, err := sysSyscall(sysSysIoctl, fd, BlkDiscard, uintptr(unsafe.Pointer(&r[0]))); err != 0 {
+ return err
+ }
+ return nil
+}
diff --git a/runtime/graphdriver/devmapper/mount.go b/runtime/graphdriver/devmapper/mount.go
new file mode 100644
index 0000000000..4f19109bf8
--- /dev/null
+++ b/runtime/graphdriver/devmapper/mount.go
@@ -0,0 +1,27 @@
+// +build linux,amd64
+
+package devmapper
+
+import (
+ "path/filepath"
+)
+
+// FIXME: this is copy-pasted from the aufs driver.
+// It should be moved into the core.
+
+var Mounted = func(mountpoint string) (bool, error) {
+ mntpoint, err := osStat(mountpoint)
+ if err != nil {
+ if osIsNotExist(err) {
+ return false, nil
+ }
+ return false, err
+ }
+ parent, err := osStat(filepath.Join(mountpoint, ".."))
+ if err != nil {
+ return false, err
+ }
+ mntpointSt := toSysStatT(mntpoint.Sys())
+ parentSt := toSysStatT(parent.Sys())
+ return mntpointSt.Dev != parentSt.Dev, nil
+}
diff --git a/runtime/graphdriver/devmapper/sys.go b/runtime/graphdriver/devmapper/sys.go
new file mode 100644
index 0000000000..5a9ab4d74b
--- /dev/null
+++ b/runtime/graphdriver/devmapper/sys.go
@@ -0,0 +1,57 @@
+// +build linux,amd64
+
+package devmapper
+
+import (
+ "os"
+ "os/exec"
+ "syscall"
+)
+
+type (
+ sysStatT syscall.Stat_t
+ sysErrno syscall.Errno
+
+ osFile struct{ *os.File }
+)
+
+var (
+ sysMount = syscall.Mount
+ sysUnmount = syscall.Unmount
+ sysCloseOnExec = syscall.CloseOnExec
+ sysSyscall = syscall.Syscall
+
+ osOpenFile = func(name string, flag int, perm os.FileMode) (*osFile, error) {
+ f, err := os.OpenFile(name, flag, perm)
+ return &osFile{File: f}, err
+ }
+ osOpen = func(name string) (*osFile, error) { f, err := os.Open(name); return &osFile{File: f}, err }
+ osNewFile = os.NewFile
+ osCreate = os.Create
+ osStat = os.Stat
+ osIsNotExist = os.IsNotExist
+ osIsExist = os.IsExist
+ osMkdirAll = os.MkdirAll
+ osRemoveAll = os.RemoveAll
+ osRename = os.Rename
+ osReadlink = os.Readlink
+
+ execRun = func(name string, args ...string) error { return exec.Command(name, args...).Run() }
+)
+
+const (
+ sysMsMgcVal = syscall.MS_MGC_VAL
+ sysMsRdOnly = syscall.MS_RDONLY
+ sysEInval = syscall.EINVAL
+ sysSysIoctl = syscall.SYS_IOCTL
+ sysEBusy = syscall.EBUSY
+
+ osORdOnly = os.O_RDONLY
+ osORdWr = os.O_RDWR
+ osOCreate = os.O_CREATE
+ osModeDevice = os.ModeDevice
+)
+
+func toSysStatT(i interface{}) *sysStatT {
+ return (*sysStatT)(i.(*syscall.Stat_t))
+}