// +build linux package devicemapper import ( "errors" "fmt" "os" "runtime" "syscall" "unsafe" "github.com/sirupsen/logrus" ) // DevmapperLogger defines methods for logging with devicemapper. type DevmapperLogger interface { DMLog(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 ) // List of errors returned when using devicemapper. 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") ErrTaskGetDeps = errors.New("dm_task_get_deps failed") ErrTaskGetInfo = errors.New("dm_task_get_info failed") ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed") ErrTaskDeferredRemove = errors.New("dm_task_deferred_remove failed") ErrTaskSetCookie = errors.New("dm_task_set_cookie failed") ErrNilCookie = errors.New("cookie ptr can't be nil") 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("Invalid AddNode type") ErrBusy = errors.New("Device is Busy") ErrDeviceIDExists = errors.New("Device Id Exists") ErrEnxio = errors.New("No such device or address") ) var ( dmSawBusy bool dmSawExist bool dmSawEnxio bool // No Such Device or Address ) type ( // Task represents a devicemapper task (like lvcreate, etc.) ; a task is needed for each ioctl // command to execute. Task struct { unmanaged *cdmTask } // Deps represents dependents (layer) of a device. Deps struct { Count uint32 Filler uint32 Device []uint64 } // Info represents information about a device. Info struct { Exists int Suspended int LiveTable int InactiveTable int OpenCount int32 EventNr uint32 Major uint32 Minor uint32 ReadOnly int TargetCount int32 DeferredRemove int } // TaskType represents a type of task TaskType int // AddNodeType represents a type of node to be added AddNodeType int ) // DeviceIDExists returns whether error conveys the information about device Id already // exist or not. This will be true if device creation or snap creation // operation fails if device or snap device already exists in pool. // Current implementation is little crude as it scans the error string // for exact pattern match. Replacing it with more robust implementation // is desirable. func DeviceIDExists(err error) bool { return fmt.Sprint(err) == fmt.Sprint(ErrDeviceIDExists) } func (t *Task) destroy() { if t != nil { DmTaskDestroy(t.unmanaged) runtime.SetFinalizer(t, nil) } } // TaskCreateNamed is a convenience function for TaskCreate when a name // will be set on the task as well func TaskCreateNamed(t TaskType, name string) (*Task, error) { task := TaskCreate(t) if task == nil { return nil, fmt.Errorf("devicemapper: Can't create task of type %d", int(t)) } if err := task.setName(name); err != nil { return nil, fmt.Errorf("devicemapper: Can't set task name %s", name) } return task, nil } // TaskCreate initializes a devicemapper task of tasktype 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) getDeps() (*Deps, error) { var deps *Deps if deps = DmTaskGetDeps(t.unmanaged); deps == nil { return nil, ErrTaskGetDeps } return deps, 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) getInfoWithDeferred() (*Info, error) { info := &Info{} if res := DmTaskGetInfoWithDeferred(t.unmanaged, info); res != 1 { return nil, ErrTaskGetInfo } return info, nil } func (t *Task) getDriverVersion() (string, error) { res := DmTaskGetDriverVersion(t.unmanaged) if res == "" { return "", ErrTaskGetDriverVersion } return res, nil } func (t *Task) getNextTarget(next unsafe.Pointer) (nextPtr unsafe.Pointer, start uint64, length uint64, targetType string, params string) { return DmGetNextTarget(t.unmanaged, next, &start, &length, &targetType, ¶ms), start, length, targetType, params } // UdevWait waits for any processes that are waiting for udev to complete the specified cookie. func UdevWait(cookie *uint) error { if res := DmUdevWait(*cookie); res != 1 { logrus.Debugf("devicemapper: Failed to wait on udev cookie %d", *cookie) return ErrUdevWait } return nil } // LogInitVerbose is an interface to initialize the verbose logger for the device mapper library. func LogInitVerbose(level int) { DmLogInitVerbose(level) } var dmLogger DevmapperLogger // LogInit initializes the logger for the device mapper library. func LogInit(logger DevmapperLogger) { dmLogger = logger LogWithErrnoInit() } // SetDevDir sets the dev folder for the device mapper library (usually /dev). func SetDevDir(dir string) error { if res := DmSetDevDir(dir); res != 1 { logrus.Debug("devicemapper: Error dm_set_dev_dir") return ErrSetDevDir } return nil } // GetLibraryVersion returns the device mapper library version. func GetLibraryVersion() (string, error) { var version string if res := DmGetLibraryVersion(&version); res != 1 { return "", ErrGetLibraryVersion } return version, nil } // UdevSyncSupported returns whether device-mapper is able to sync with udev // // This is essential otherwise race conditions can arise where both udev and // device-mapper attempt to create and destroy devices. func UdevSyncSupported() bool { return DmUdevGetSyncSupport() != 0 } // UdevSetSyncSupport allows setting whether the udev sync should be enabled. // The return bool indicates the state of whether the sync is enabled. func UdevSetSyncSupport(enable bool) bool { if enable { DmUdevSetSyncSupport(1) } else { DmUdevSetSyncSupport(0) } return UdevSyncSupported() } // CookieSupported returns whether the version of device-mapper supports the // use of cookie's in the tasks. // This is largely a lower level call that other functions use. func CookieSupported() bool { return DmCookieSupported() != 0 } // RemoveDevice is a useful helper for cleaning up a device. func RemoveDevice(name string) error { task, err := TaskCreateNamed(deviceRemove, name) if task == nil { return err } var cookie uint if err := task.setCookie(&cookie, 0); err != nil { return fmt.Errorf("devicemapper: Can not set cookie: %s", err) } defer UdevWait(&cookie) dmSawBusy = false // reset before the task is run if err = task.run(); err != nil { if dmSawBusy { return ErrBusy } return fmt.Errorf("devicemapper: Error running RemoveDevice %s", err) } return nil } // RemoveDeviceDeferred is a useful helper for cleaning up a device, but deferred. func RemoveDeviceDeferred(name string) error { logrus.Debugf("devicemapper: RemoveDeviceDeferred START(%s)", name) defer logrus.Debugf("devicemapper: RemoveDeviceDeferred END(%s)", name) task, err := TaskCreateNamed(deviceRemove, name) if task == nil { return err } if err := DmTaskDeferredRemove(task.unmanaged); err != 1 { return ErrTaskDeferredRemove } if err = task.run(); err != nil { return fmt.Errorf("devicemapper: Error running RemoveDeviceDeferred %s", err) } return nil } // CancelDeferredRemove cancels a deferred remove for a device. func CancelDeferredRemove(deviceName string) error { task, err := TaskCreateNamed(deviceTargetMsg, deviceName) if task == nil { return err } if err := task.setSector(0); err != nil { return fmt.Errorf("devicemapper: Can't set sector %s", err) } if err := task.setMessage(fmt.Sprintf("@cancel_deferred_remove")); err != nil { return fmt.Errorf("devicemapper: Can't set message %s", err) } dmSawBusy = false dmSawEnxio = false if err := task.run(); err != nil { // A device might be being deleted already if dmSawBusy { return ErrBusy } else if dmSawEnxio { return ErrEnxio } return fmt.Errorf("devicemapper: Error running CancelDeferredRemove %s", err) } return nil } // GetBlockDeviceSize returns the size of a block device identified by the specified file. func GetBlockDeviceSize(file *os.File) (uint64, error) { size, err := ioctlBlkGetSize64(file.Fd()) if err != nil { logrus.Errorf("devicemapper: Error getblockdevicesize: %s", err) return 0, ErrGetBlockSize } return uint64(size), nil } // BlockDeviceDiscard runs discard for the given path. // This is used as a workaround for the kernel not discarding block so // on the thin pool when we remove a thinp device, so we do it // manually func BlockDeviceDiscard(path string) error { file, err := os.OpenFile(path, os.O_RDWR, 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 } // CreatePool is the programmatic example of "dmsetup create". // It creates a device with the specified poolName, data and metadata file and block size. func CreatePool(poolName string, dataFile, metadataFile *os.File, poolBlockSize uint32) error { task, err := TaskCreateNamed(deviceCreate, poolName) if task == nil { return err } size, err := GetBlockDeviceSize(dataFile) if err != nil { return fmt.Errorf("devicemapper: Can't get data size %s", err) } params := fmt.Sprintf("%s %s %d 32768 1 skip_block_zeroing", metadataFile.Name(), dataFile.Name(), poolBlockSize) if err := task.addTarget(0, size/512, "thin-pool", params); err != nil { return fmt.Errorf("devicemapper: Can't add target %s", err) } var cookie uint var flags uint16 flags = DmUdevDisableSubsystemRulesFlag | DmUdevDisableDiskRulesFlag | DmUdevDisableOtherRulesFlag if err := task.setCookie(&cookie, flags); err != nil { return fmt.Errorf("devicemapper: Can't set cookie %s", err) } defer UdevWait(&cookie) if err := task.run(); err != nil { return fmt.Errorf("devicemapper: Error running deviceCreate (CreatePool) %s", err) } return nil } // ReloadPool is the programmatic example of "dmsetup reload". // It reloads the table with the specified poolName, data and metadata file and block size. func ReloadPool(poolName string, dataFile, metadataFile *os.File, poolBlockSize uint32) error { task, err := TaskCreateNamed(deviceReload, poolName) if task == nil { return err } size, err := GetBlockDeviceSize(dataFile) if err != nil { return fmt.Errorf("devicemapper: Can't get data size %s", err) } params := fmt.Sprintf("%s %s %d 32768 1 skip_block_zeroing", metadataFile.Name(), dataFile.Name(), poolBlockSize) if err := task.addTarget(0, size/512, "thin-pool", params); err != nil { return fmt.Errorf("devicemapper: Can't add target %s", err) } if err := task.run(); err != nil { return fmt.Errorf("devicemapper: Error running deviceCreate %s", err) } return nil } // GetDeps is the programmatic example of "dmsetup deps". // It outputs a list of devices referenced by the live table for the specified device. func GetDeps(name string) (*Deps, error) { task, err := TaskCreateNamed(deviceDeps, name) if task == nil { return nil, err } if err := task.run(); err != nil { return nil, err } return task.getDeps() } // GetInfo is the programmatic example of "dmsetup info". // It outputs some brief information about the device. func GetInfo(name string) (*Info, error) { task, err := TaskCreateNamed(deviceInfo, name) if task == nil { return nil, err } if err := task.run(); err != nil { return nil, err } return task.getInfo() } // GetInfoWithDeferred is the programmatic example of "dmsetup info", but deferred. // It outputs some brief information about the device. func GetInfoWithDeferred(name string) (*Info, error) { task, err := TaskCreateNamed(deviceInfo, name) if task == nil { return nil, err } if err := task.run(); err != nil { return nil, err } return task.getInfoWithDeferred() } // GetDriverVersion is the programmatic example of "dmsetup version". // It outputs version information of the driver. func GetDriverVersion() (string, error) { task := TaskCreate(deviceVersion) if task == nil { return "", fmt.Errorf("devicemapper: Can't create deviceVersion task") } if err := task.run(); err != nil { return "", err } return task.getDriverVersion() } // GetStatus is the programmatic example of "dmsetup status". // It outputs status information for the specified device name. func GetStatus(name string) (uint64, uint64, string, string, error) { task, err := TaskCreateNamed(deviceStatus, name) if task == nil { logrus.Debugf("devicemapper: GetStatus() Error TaskCreateNamed: %s", err) return 0, 0, "", "", err } if err := task.run(); err != nil { logrus.Debugf("devicemapper: GetStatus() Error Run: %s", err) return 0, 0, "", "", err } devinfo, err := task.getInfo() if err != nil { logrus.Debugf("devicemapper: GetStatus() Error GetInfo: %s", err) return 0, 0, "", "", err } if devinfo.Exists == 0 { logrus.Debugf("devicemapper: GetStatus() Non existing device %s", name) return 0, 0, "", "", fmt.Errorf("devicemapper: Non existing device %s", name) } _, start, length, targetType, params := task.getNextTarget(unsafe.Pointer(nil)) return start, length, targetType, params, nil } // GetTable is the programmatic example for "dmsetup table". // It outputs the current table for the specified device name. func GetTable(name string) (uint64, uint64, string, string, error) { task, err := TaskCreateNamed(deviceTable, name) if task == nil { logrus.Debugf("devicemapper: GetTable() Error TaskCreateNamed: %s", err) return 0, 0, "", "", err } if err := task.run(); err != nil { logrus.Debugf("devicemapper: GetTable() Error Run: %s", err) return 0, 0, "", "", err } devinfo, err := task.getInfo() if err != nil { logrus.Debugf("devicemapper: GetTable() Error GetInfo: %s", err) return 0, 0, "", "", err } if devinfo.Exists == 0 { logrus.Debugf("devicemapper: GetTable() Non existing device %s", name) return 0, 0, "", "", fmt.Errorf("devicemapper: Non existing device %s", name) } _, start, length, targetType, params := task.getNextTarget(unsafe.Pointer(nil)) return start, length, targetType, params, nil } // SetTransactionID sets a transaction id for the specified device name. func SetTransactionID(poolName string, oldID uint64, newID uint64) error { task, err := TaskCreateNamed(deviceTargetMsg, poolName) if task == nil { return err } if err := task.setSector(0); err != nil { return fmt.Errorf("devicemapper: Can't set sector %s", err) } if err := task.setMessage(fmt.Sprintf("set_transaction_id %d %d", oldID, newID)); err != nil { return fmt.Errorf("devicemapper: Can't set message %s", err) } if err := task.run(); err != nil { return fmt.Errorf("devicemapper: Error running SetTransactionID %s", err) } return nil } // SuspendDevice is the programmatic example of "dmsetup suspend". // It suspends the specified device. func SuspendDevice(name string) error { task, err := TaskCreateNamed(deviceSuspend, name) if task == nil { return err } if err := task.run(); err != nil { return fmt.Errorf("devicemapper: Error running deviceSuspend %s", err) } return nil } // ResumeDevice is the programmatic example of "dmsetup resume". // It un-suspends the specified device. func ResumeDevice(name string) error { task, err := TaskCreateNamed(deviceResume, name) if task == nil { return err } var cookie uint if err := task.setCookie(&cookie, 0); err != nil { return fmt.Errorf("devicemapper: Can't set cookie %s", err) } defer UdevWait(&cookie) if err := task.run(); err != nil { return fmt.Errorf("devicemapper: Error running deviceResume %s", err) } return nil } // CreateDevice creates a device with the specified poolName with the specified device id. func CreateDevice(poolName string, deviceID int) error { logrus.Debugf("devicemapper: CreateDevice(poolName=%v, deviceID=%v)", poolName, deviceID) task, err := TaskCreateNamed(deviceTargetMsg, poolName) if task == nil { return err } if err := task.setSector(0); err != nil { return fmt.Errorf("devicemapper: Can't set sector %s", err) } if err := task.setMessage(fmt.Sprintf("create_thin %d", deviceID)); err != nil { return fmt.Errorf("devicemapper: Can't set message %s", err) } dmSawExist = false // reset before the task is run if err := task.run(); err != nil { // Caller wants to know about ErrDeviceIDExists so that it can try with a different device id. if dmSawExist { return ErrDeviceIDExists } return fmt.Errorf("devicemapper: Error running CreateDevice %s", err) } return nil } // DeleteDevice deletes a device with the specified poolName with the specified device id. func DeleteDevice(poolName string, deviceID int) error { task, err := TaskCreateNamed(deviceTargetMsg, poolName) if task == nil { return err } if err := task.setSector(0); err != nil { return fmt.Errorf("devicemapper: Can't set sector %s", err) } if err := task.setMessage(fmt.Sprintf("delete %d", deviceID)); err != nil { return fmt.Errorf("devicemapper: Can't set message %s", err) } dmSawBusy = false if err := task.run(); err != nil { if dmSawBusy { return ErrBusy } return fmt.Errorf("devicemapper: Error running DeleteDevice %s", err) } return nil } // ActivateDevice activates the device identified by the specified // poolName, name and deviceID with the specified size. func ActivateDevice(poolName string, name string, deviceID int, size uint64) error { return activateDevice(poolName, name, deviceID, size, "") } // ActivateDeviceWithExternal activates the device identified by the specified // poolName, name and deviceID with the specified size. func ActivateDeviceWithExternal(poolName string, name string, deviceID int, size uint64, external string) error { return activateDevice(poolName, name, deviceID, size, external) } func activateDevice(poolName string, name string, deviceID int, size uint64, external string) error { task, err := TaskCreateNamed(deviceCreate, name) if task == nil { return err } var params string if len(external) > 0 { params = fmt.Sprintf("%s %d %s", poolName, deviceID, external) } else { params = fmt.Sprintf("%s %d", poolName, deviceID) } if err := task.addTarget(0, size/512, "thin", params); err != nil { return fmt.Errorf("devicemapper: Can't add target %s", err) } if err := task.setAddNode(addNodeOnCreate); err != nil { return fmt.Errorf("devicemapper: Can't add node %s", err) } var cookie uint if err := task.setCookie(&cookie, 0); err != nil { return fmt.Errorf("devicemapper: Can't set cookie %s", err) } defer UdevWait(&cookie) if err := task.run(); err != nil { return fmt.Errorf("devicemapper: Error running deviceCreate (ActivateDevice) %s", err) } return nil } // CreateSnapDevice creates a snapshot based on the device identified by the baseName and baseDeviceId, func 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 := TaskCreateNamed(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("devicemapper: Can't set sector %s", err) } if err := task.setMessage(fmt.Sprintf("create_snap %d %d", deviceID, baseDeviceID)); err != nil { if doSuspend { ResumeDevice(baseName) } return fmt.Errorf("devicemapper: Can't set message %s", err) } dmSawExist = false // reset before the task is run if err := task.run(); err != nil { if doSuspend { ResumeDevice(baseName) } // Caller wants to know about ErrDeviceIDExists so that it can try with a different device id. if dmSawExist { return ErrDeviceIDExists } return fmt.Errorf("devicemapper: Error running deviceCreate (createSnapDevice) %s", err) } if doSuspend { if err := ResumeDevice(baseName); err != nil { return err } } return nil }