| // +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, ¶ms), |
| 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" |
| 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 |
| } |