From 74c8f7af756ed03131aee051b0ccb926b77e04db Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 27 Nov 2013 07:16:34 +0000 Subject: [PATCH] Refactor attach loop device in pure Go --- graphdriver/devmapper/deviceset.go | 10 +- graphdriver/devmapper/devmapper.go | 125 +++++++++++++-- graphdriver/devmapper/devmapper_wrapper.go | 178 +++++---------------- graphdriver/devmapper/sys.go | 8 +- 4 files changed, 160 insertions(+), 161 deletions(-) diff --git a/graphdriver/devmapper/deviceset.go b/graphdriver/devmapper/deviceset.go index 8c2556104d..77d865ad99 100644 --- a/graphdriver/devmapper/deviceset.go +++ b/graphdriver/devmapper/deviceset.go @@ -383,7 +383,7 @@ func (devices *DeviceSet) ResizePool(size int64) error { return fmt.Errorf("Can't shrink file") } - dataloopback := FindLoopDeviceFor(&osFile{File: datafile}) + dataloopback := FindLoopDeviceFor(datafile) if dataloopback == nil { return fmt.Errorf("Unable to find loopback mount for: %s", datafilename) } @@ -395,7 +395,7 @@ func (devices *DeviceSet) ResizePool(size int64) error { } defer metadatafile.Close() - metadataloopback := FindLoopDeviceFor(&osFile{File: metadatafile}) + metadataloopback := FindLoopDeviceFor(metadatafile) if metadataloopback == nil { return fmt.Errorf("Unable to find loopback mount for: %s", metadatafilename) } @@ -491,14 +491,14 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { if info.Exists == 0 { utils.Debugf("Pool doesn't exist. Creating it.") - dataFile, err := AttachLoopDevice(data) + dataFile, err := attachLoopDevice(data) if err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } defer dataFile.Close() - metadataFile, err := AttachLoopDevice(metadata) + metadataFile, err := attachLoopDevice(metadata) if err != nil { utils.Debugf("\n--->Err: %s\n", err) return err @@ -637,7 +637,7 @@ func (devices *DeviceSet) deactivateDevice(hash string) error { // or b) the 1 second timeout expires. func (devices *DeviceSet) waitRemove(hash string) error { utils.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, hash) - defer utils.Debugf("[deviceset %s] waitRemove END", devices.devicePrefix, hash) + defer utils.Debugf("[deviceset %s] waitRemove(%) END", devices.devicePrefix, hash) devname, err := devices.byHash(hash) if err != nil { return err diff --git a/graphdriver/devmapper/devmapper.go b/graphdriver/devmapper/devmapper.go index 89c36d45ad..305ed183d4 100644 --- a/graphdriver/devmapper/devmapper.go +++ b/graphdriver/devmapper/devmapper.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/dotcloud/docker/utils" "runtime" + "unsafe" ) type DevmapperLogger interface { @@ -177,15 +178,6 @@ func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64, start, length, targetType, params } -func AttachLoopDevice(filename string) (*osFile, error) { - var fd int - res := DmAttachLoopDevice(filename, &fd) - if res == "" { - return nil, ErrAttachLoopbackDevice - } - return &osFile{File: osNewFile(uintptr(fd), res)}, nil -} - func getLoopbackBackingFile(file *osFile) (uint64, uint64, error) { dev, inode, err := DmGetLoopbackBackingFile(file.Fd()) if err != 0 { @@ -223,11 +215,10 @@ func FindLoopDeviceFor(file *osFile) *osFile { continue } - dev, inode, err := getLoopbackBackingFile(&osFile{File: file}) + dev, inode, err := getLoopbackBackingFile(file) if err == nil && dev == targetDevice && inode == targetInode { - return &osFile{File: file} + return file } - file.Close() } @@ -420,7 +411,7 @@ func suspendDevice(name string) error { return err } if err := task.Run(); err != nil { - return fmt.Errorf("Error running DeviceSuspend") + return fmt.Errorf("Error running DeviceSuspend: %s", err) } return nil } @@ -437,7 +428,7 @@ func resumeDevice(name string) error { } if err := task.Run(); err != nil { - return fmt.Errorf("Error running DeviceSuspend") + return fmt.Errorf("Error running DeviceResume") } UdevWait(cookie) @@ -574,3 +565,109 @@ func (devices *DeviceSet) createSnapDevice(poolName string, deviceId int, baseNa return nil } + +type 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 +} + +// attachLoopDevice attaches the given sparse file to the next +// available loopback device. It returns an opened *osFile. +func attachLoopDevice(filename string) (loop *osFile, err error) { + startIndex := 0 + + // 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. + if f, err := osOpenFile("/dev/loop-control", osORdOnly, 0644); err == nil { + if index, _, err := sysSyscall(sysSysIoctl, f.Fd(), LoopCtlGetFree, 0); err != 0 { + utils.Debugf("Error retrieving the next available loopback: %s", err) + } else if index > 0 { + startIndex = int(index) + } + f.Close() + } + + // Open the given sparse file (use OpenFile because Open sets O_CLOEXEC) + f, err := osOpenFile(filename, osORdWr, 0644) + if err != nil { + return nil, err + } + defer f.Close() + + var ( + target string + loopFile *osFile + ) + // Start looking for a free /dev/loop + for i := startIndex; ; { + target = fmt.Sprintf("/dev/loop%d", i) + + fi, err := osStat(target) + if err != nil { + if osIsNotExist(err) { + utils.Errorf("There are no more loopback device available.") + } + } + + // FIXME: Check here if target is a block device (in C: S_ISBLK(mode)) + if fi.IsDir() { + } + + // Open the targeted loopback (use OpenFile because Open sets O_CLOEXEC) + loopFile, err = osOpenFile(target, osORdWr, 0644) + if err != nil { + return nil, err + } + + // Try to attach to the loop file + if _, _, err := sysSyscall(sysSysIoctl, loopFile.Fd(), LoopSetFd, f.Fd()); err != 0 { + 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, err + } + } else { + // In case of success, we finished. Break the loop. + break + } + // In case of EBUSY error, the loop keep going. + } + + // This can't happen, but let's be sure + if loopFile == nil { + return nil, fmt.Errorf("Unreachable code reached! Error attaching %s to a loopback device.", filename) + } + + // Set the status of the loopback device + var loopInfo LoopInfo64 + + // Due to type incompatibility (string vs [64]uint8), we copy data + copy(loopInfo.loFileName[:], target[:]) + loopInfo.loOffset = 0 + loopInfo.loFlags = LoFlagsAutoClear + + if _, _, err := sysSyscall(sysSysIoctl, loopFile.Fd(), LoopSetStatus64, uintptr(unsafe.Pointer(&loopInfo))); err != 0 { + // If the call failed, then free the loopback device + utils.Errorf("Cannot set up loopback device info: %s", err) + if _, _, err := sysSyscall(sysSysIoctl, loopFile.Fd(), LoopClrFd, 0); err != 0 { + utils.Errorf("Error while cleaning up the loopback device") + } + loopFile.Close() + return nil, err + } + + return loopFile, nil +} diff --git a/graphdriver/devmapper/devmapper_wrapper.go b/graphdriver/devmapper/devmapper_wrapper.go index 818c968bbf..0ed5bcd332 100644 --- a/graphdriver/devmapper/devmapper_wrapper.go +++ b/graphdriver/devmapper/devmapper_wrapper.go @@ -2,124 +2,14 @@ package devmapper /* #cgo LDFLAGS: -L. -ldevmapper -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include - -#ifndef LOOP_CTL_GET_FREE -#define LOOP_CTL_GET_FREE 0x4C82 -#endif - -// FIXME: this could easily be rewritten in go -char* attach_loop_device(const char *filename, int *loop_fd_out) -{ - struct loop_info64 loopinfo = {0}; - struct stat st; - char buf[64]; - int i, loop_fd, fd, start_index; - char* loopname; - - - *loop_fd_out = -1; - - start_index = 0; - fd = open("/dev/loop-control", O_RDONLY); - if (fd >= 0) { - start_index = ioctl(fd, LOOP_CTL_GET_FREE); - close(fd); - - if (start_index < 0) - start_index = 0; - } - - fd = open(filename, O_RDWR); - if (fd < 0) { - perror("open"); - return NULL; - } - - loop_fd = -1; - for (i = start_index ; loop_fd < 0 ; i++ ) { - if (sprintf(buf, "/dev/loop%d", i) < 0) { - close(fd); - return NULL; - } - - if (stat(buf, &st)) { - if (!S_ISBLK(st.st_mode)) { - fprintf(stderr, "[error] Loopback device %s is not a block device.\n", buf); - } else if (errno == ENOENT) { - fprintf(stderr, "[error] There are no more loopback device available.\n"); - } else { - fprintf(stderr, "[error] Unkown error trying to stat the loopback device %s (errno: %d).\n", buf, errno); - } - close(fd); - return NULL; - } - - loop_fd = open(buf, O_RDWR); - if (loop_fd < 0 && errno == ENOENT) { - fprintf(stderr, "[error] The loopback device %s does not exists.\n", buf); - close(fd); - return NULL; - } else if (loop_fd < 0) { - fprintf(stderr, "[error] Unkown error openning the loopback device %s. (errno: %d)\n", buf, errno); - continue; - } - - if (ioctl(loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) { - int errsv = errno; - close(loop_fd); - loop_fd = -1; - if (errsv != EBUSY) { - close(fd); - fprintf(stderr, "cannot set up loopback device %s: %s", buf, strerror(errsv)); - return NULL; - } - continue; - } - - close(fd); - - strncpy((char*)loopinfo.lo_file_name, buf, LO_NAME_SIZE); - loopinfo.lo_offset = 0; - loopinfo.lo_flags = LO_FLAGS_AUTOCLEAR; - - if (ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo) < 0) { - perror("ioctl LOOP_SET_STATUS64"); - if (ioctl(loop_fd, LOOP_CLR_FD, 0) < 0) { - perror("ioctl LOOP_CLR_FD"); - } - close(loop_fd); - fprintf (stderr, "cannot set up loopback device info"); - return (NULL); - } - - loopname = strdup(buf); - if (loopname == NULL) { - close(loop_fd); - return (NULL); - } - - *loop_fd_out = loop_fd; - return (loopname); - } - - return (NULL); -} +#include // FIXME: present only for defines, maybe we can remove it? +#include // FIXME: present only for BLKGETSIZE64, maybe we can remove it? +// 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, ...) +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; @@ -135,7 +25,6 @@ static void log_with_errno_init() { dm_log_with_errno_init(log_cb); } - */ import "C" @@ -147,6 +36,19 @@ type ( CDmTask C.struct_dm_task ) +// FIXME: Make sure the values are defined in C +const ( + LoopSetFd = C.LOOP_SET_FD + LoopCtlGetFree = C.LOOP_CTL_GET_FREE + LoopSetStatus64 = C.LOOP_SET_STATUS64 + LoopClrFd = C.LOOP_CLR_FD + 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 ( DmAttachLoopDevice = dmAttachLoopDeviceFct DmGetBlockSize = dmGetBlockSizeFct @@ -185,28 +87,26 @@ func dmTaskCreateFct(taskType int) *CDmTask { } func dmTaskRunFct(task *CDmTask) int { - return int(C.dm_task_run((*C.struct_dm_task)(task))) + 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)) + 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)) + 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))) + 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 { @@ -214,13 +114,11 @@ func dmTaskSetCookieFct(task *CDmTask, cookie *uint, flags uint16) int { defer func() { *cookie = uint(cCookie) }() - return int(C.dm_task_set_cookie((*C.struct_dm_task)(task), &cCookie, - C.uint16_t(flags))) + 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))) + return int(C.dm_task_set_add_node((*C.struct_dm_task)(task), C.dm_add_node_t(addNode))) } func dmTaskSetRoFct(task *CDmTask) int { @@ -236,14 +134,12 @@ func dmTaskAddTargetFct(task *CDmTask, 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)) + return int(C.dm_task_add_target((*C.struct_dm_task)(task), C.uint64_t(start), C.uint64_t(size), Cttype, Cparams)) } func dmGetLoopbackBackingFileFct(fd uintptr) (uint64, uint64, sysErrno) { var lo64 C.struct_loop_info64 - _, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_GET_STATUS64, - uintptr(unsafe.Pointer(&lo64))) + _, _, err := sysSyscall(sysSysIoctl, fd, C.LOOP_GET_STATUS64, uintptr(unsafe.Pointer(&lo64))) return uint64(lo64.lo_device), uint64(lo64.lo_inode), sysErrno(err) } @@ -287,23 +183,23 @@ func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, targ *params = C.GoString(Cparams) }() - nextp := C.dm_get_next_target((*C.struct_dm_task)(task), - unsafe.Pointer(next), &Cstart, &Clength, &CtargetType, &Cparams) + nextp := C.dm_get_next_target((*C.struct_dm_task)(task), unsafe.Pointer(next), &Cstart, &Clength, &CtargetType, &Cparams) return uintptr(nextp) } func dmAttachLoopDeviceFct(filename string, fd *int) string { - cFilename := C.CString(filename) - defer free(cFilename) + return "" + // cFilename := C.CString(filename) + // defer free(cFilename) - var cFd C.int - defer func() { - *fd = int(cFd) - }() + // var cFd C.int + // defer func() { + // *fd = int(cFd) + // }() - ret := C.attach_loop_device(cFilename, &cFd) - defer free(ret) - return C.GoString(ret) + // ret := C.attach_loop_device(cFilename, &cFd) + // defer free(ret) + // return C.GoString(ret) } func getBlockSizeFct(fd uintptr, size *uint64) sysErrno { diff --git a/graphdriver/devmapper/sys.go b/graphdriver/devmapper/sys.go index 60bafb5f6d..152e68285b 100644 --- a/graphdriver/devmapper/sys.go +++ b/graphdriver/devmapper/sys.go @@ -19,7 +19,11 @@ var ( sysCloseOnExec = syscall.CloseOnExec sysSyscall = syscall.Syscall - osOpenFile = os.OpenFile + 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 @@ -40,7 +44,9 @@ const ( 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 )