From 739af0a17f6a5a9956bbc9fd1e81e4d40bff8167 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:14:31 +0200 Subject: [PATCH 001/126] Add libdevmapper wrapper --- devmapper/devmapper.go | 349 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 349 insertions(+) create mode 100644 devmapper/devmapper.go diff --git a/devmapper/devmapper.go b/devmapper/devmapper.go new file mode 100644 index 0000000000..f007091827 --- /dev/null +++ b/devmapper/devmapper.go @@ -0,0 +1,349 @@ +package devmapper + +/* +#cgo LDFLAGS: -L. -ldevmapper +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static 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) { + 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) || !S_ISBLK(st.st_mode)) { + close(fd); + return NULL; + } + + loop_fd = open(buf, O_RDWR); + if (loop_fd < 0 && errno == ENOENT) { + close(fd); + fprintf (stderr, "no available loopback device!"); + return NULL; + } else if (loop_fd < 0) + continue; + + if (ioctl (loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) { + close(loop_fd); + loop_fd = -1; + if (errno != EBUSY) { + close (fd); + fprintf (stderr, "cannot set up loopback device %s", buf); + 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) { + ioctl(loop_fd, LOOP_CLR_FD, 0); + 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; +} + +static int64_t +get_block_size(int fd) +{ + uint64_t size; + if (ioctl(fd, BLKGETSIZE64, &size) == -1) + return -1; + return (int64_t)size; +} + + +*/ +import "C" +import "unsafe" +import "fmt" +import "runtime" +import "os" + +func SetDevDir(dir string) error { + c_dir := C.CString(dir) + defer C.free(unsafe.Pointer(c_dir)) + res := C.dm_set_dev_dir(c_dir) + if res != 1 { + return fmt.Errorf("dm_set_dev_dir failed") + } + return nil +} + +func GetLibraryVersion() (string, error) { + buffer := (*C.char)(C.malloc(128)) + defer C.free(unsafe.Pointer(buffer)) + res := C.dm_get_library_version(buffer, 128) + if res != 1 { + return "", fmt.Errorf("dm_get_library_version failed") + } else { + return C.GoString(buffer), nil + } +} + +type TaskType int + +const ( + DeviceCreate TaskType = iota + DeviceReload + DeviceRemove + DeviceRemoveAll + DeviceSuspend + DeviceResume + DeviceInfo + DeviceDeps + DeviceRename + DeviceVersion + DeviceStatus + DeviceTable + DeviceWaitevent + DeviceList + DeviceClear + DeviceMknodes + DeviceListVersions + DeviceTargetMsg + DeviceSetGeometry +) + +type Task struct { + unmanaged *C.struct_dm_task +} + +type Info struct { + Exists int + Suspended int + LiveTable int + InactiveTable int + OpenCount int32 + EventNr uint32 + Major uint32 + Minor uint32 + ReadOnly int + TargetCount int32 +} + +func (t *Task) destroy() { + if t != nil { + C.dm_task_destroy(t.unmanaged) + runtime.SetFinalizer(t, nil) + } +} + +func TaskCreate(tasktype TaskType) *Task { + c_task := C.dm_task_create(C.int(int(tasktype))) + if c_task == nil { + return nil + } + task := &Task{c_task} + runtime.SetFinalizer(task, (*Task).destroy) + return task +} + +func (t *Task) Run() error { + res := C.dm_task_run(t.unmanaged) + if res != 1 { + return fmt.Errorf("dm_task_run failed") + } + return nil +} + +func (t *Task) SetName(name string) error { + c_name := C.CString(name) + defer C.free(unsafe.Pointer(c_name)) + + res := C.dm_task_set_name(t.unmanaged, c_name) + if res != 1 { + return fmt.Errorf("dm_task_set_name failed") + } + return nil +} + +func (t *Task) SetMessage(message string) error { + c_message := C.CString(message) + defer C.free(unsafe.Pointer(c_message)) + + res := C.dm_task_set_message(t.unmanaged, c_message) + if res != 1 { + return fmt.Errorf("dm_task_set_message failed") + } + return nil +} + +func (t *Task) SetSector(sector uint64) error { + res := C.dm_task_set_sector(t.unmanaged, C.uint64_t(sector)) + if res != 1 { + return fmt.Errorf("dm_task_set_add_node failed") + } + return nil +} + +func (t *Task) SetCookie(cookie *uint32, flags uint16) error { + var c_cookie C.uint32_t + c_cookie = C.uint32_t(*cookie) + res := C.dm_task_set_cookie(t.unmanaged, &c_cookie, C.uint16_t(flags)) + if res != 1 { + return fmt.Errorf("dm_task_set_add_node failed") + } + *cookie = uint32(c_cookie) + return nil +} + +func (t *Task) SetRo() error { + res := C.dm_task_set_ro(t.unmanaged) + if res != 1 { + return fmt.Errorf("dm_task_set_ro failed") + } + return nil +} + +func (t *Task) AddTarget(start uint64, size uint64, ttype string, params string) error { + c_ttype := C.CString(ttype) + defer C.free(unsafe.Pointer(c_ttype)) + + c_params := C.CString(params) + defer C.free(unsafe.Pointer(c_params)) + + res := C.dm_task_add_target(t.unmanaged, C.uint64_t(start), C.uint64_t(size), c_ttype, c_params) + if res != 1 { + return fmt.Errorf("dm_task_add_target failed") + } + return nil +} + +func (t *Task) GetDriverVersion() (string, error) { + buffer := (*C.char)(C.malloc(128)) + defer C.free(unsafe.Pointer(buffer)) + + res := C.dm_task_get_driver_version(t.unmanaged, buffer, 128) + if res != 1 { + return "", fmt.Errorf("dm_task_get_driver_version") + } else { + return C.GoString(buffer), nil + } +} + +func (t *Task) GetInfo() (*Info, error) { + c_info := C.struct_dm_info{} + res := C.dm_task_get_info(t.unmanaged, &c_info) + if res != 1 { + return nil, fmt.Errorf("dm_task_get_driver_version") + } else { + info := &Info{} + info.Exists = int(c_info.exists) + info.Suspended = int(c_info.suspended) + info.LiveTable = int(c_info.live_table) + info.InactiveTable = int(c_info.inactive_table) + info.OpenCount = int32(c_info.open_count) + info.EventNr = uint32(c_info.event_nr) + info.Major = uint32(c_info.major) + info.Minor = uint32(c_info.minor) + info.ReadOnly = int(c_info.read_only) + info.TargetCount = int32(c_info.target_count) + + return info, nil + } +} + +func (t *Task) GetNextTarget(next uintptr) (uintptr, uint64, uint64, string, string) { + nextp := unsafe.Pointer(next) + var c_start C.uint64_t + var c_length C.uint64_t + var c_target_type *C.char + var c_params *C.char + + nextp = C.dm_get_next_target(t.unmanaged, nextp, &c_start, &c_length, &c_target_type, &c_params) + + target_type := C.GoString(c_target_type) + params := C.GoString(c_params) + + return uintptr(nextp), uint64(c_start), uint64(c_length), target_type, params +} + +func AttachLoopDevice(filename string) (*os.File, error) { + c_filename := C.CString(filename) + defer C.free(unsafe.Pointer(c_filename)) + + var fd C.int + res := C.attach_loop_device(c_filename, &fd) + if res == nil { + return nil, fmt.Errorf("error loopback mounting") + } + file := os.NewFile(uintptr(fd), C.GoString(res)) + C.free(unsafe.Pointer(res)) + return file, nil +} + +func GetBlockDeviceSize(file *os.File) (uint64, error) { + fd := file.Fd() + size := C.get_block_size(C.int(fd)) + if size == -1 { + return 0, fmt.Errorf("Can't get block size") + } + return uint64(size), nil + +} + +func UdevWait(cookie uint32) error { + res := C.dm_udev_wait(C.uint32_t(cookie)) + if res != 1 { + return fmt.Errorf("Failed to wait on udev cookie %d", cookie) + } + return nil +} + +func LogInitVerbose(level int) { + C.dm_log_init_verbose(C.int(level)) +} From 0b12702c0c9753cbb18a942bac2600b036e4f80e Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:18:23 +0200 Subject: [PATCH 002/126] devmapper: Add DeviceSet device-mapper helper This is a module that uses the device-mapper create CoW snapshots You instantiate a DeviceSetDM object on a specified root (/var/lib/docker), and it will create a subdirectory there called "loopback". It will contain two sparse files which are loopback mounted into a thin-pool device-mapper device called "docker-pool". We then create a base snapshot in the pool with an empty filesystem which can be used as a base for docker snapshots. It also keeps track of the mapping between docker image ids and the snapshots in the pool. Typical use of is something like (without error checking): devices = NewDeviceSetDM("/var/lib/docker") devices.AddDevice(imageId, "") // "" is the base image id devices.MountDevice(imageId, "/mnt/image") ... extract base image to /mnt/image devices.AddDevice(containerId, imageId) devices.MountDevice(containerId, "/mnt/container") ... start container at /mnt/container --- devmapper/deviceset_devmapper.go | 895 +++++++++++++++++++++++++++++++ 1 file changed, 895 insertions(+) create mode 100644 devmapper/deviceset_devmapper.go diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go new file mode 100644 index 0000000000..d7e122cf28 --- /dev/null +++ b/devmapper/deviceset_devmapper.go @@ -0,0 +1,895 @@ +package devmapper + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "os/exec" + "path" + "path/filepath" + "syscall" +) + +const defaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024 +const defaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 +const 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"` +} + +type MetaData struct { + Devices map[string]*DevInfo `json:devices` +} + +type DeviceSetDM struct { + initialized bool + root string + MetaData + TransactionId uint64 + NewTransactionId uint64 + nextFreeDevice int +} + +func getDevName(name string) string { + return "/dev/mapper/" + name +} + +func (info *DevInfo) Name() string { + hash := info.Hash + if hash == "" { + hash = "base" + } + return fmt.Sprintf("docker-%s", hash) +} + +func (info *DevInfo) DevName() string { + return getDevName(info.Name()) +} + +func (devices *DeviceSetDM) loopbackDir() string { + return path.Join(devices.root, "loopback") +} + +func (devices *DeviceSetDM) jsonFile() string { + return path.Join(devices.loopbackDir(), "json") +} + +func (devices *DeviceSetDM) getPoolName() string { + return "docker-pool" +} + +func (devices *DeviceSetDM) getPoolDevName() string { + return getDevName(devices.getPoolName()) +} + +func (devices *DeviceSetDM) 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)) + } + err := task.SetName(name) + if err != nil { + return nil, fmt.Errorf("Can't set task name %s", name) + } + return task, nil +} + +func (devices *DeviceSetDM) getInfo(name string) (*Info, error) { + task, err := devices.createTask(DeviceInfo, name) + if task == nil { + return nil, err + } + err = task.Run() + if err != nil { + return nil, err + } + info, err := task.GetInfo() + if err != nil { + return nil, err + } + return info, nil +} + +func (devices *DeviceSetDM) getStatus(name string) (uint64, uint64, string, string, error) { + task, err := devices.createTask(DeviceStatus, name) + if task == nil { + return 0, 0, "", "", err + } + err = task.Run() + if err != nil { + return 0, 0, "", "", err + } + + devinfo, err := task.GetInfo() + if err != nil { + return 0, 0, "", "", err + } + if devinfo.Exists == 0 { + return 0, 0, "", "", fmt.Errorf("Non existing device %s", name) + } + + var next uintptr = 0 + next, start, length, target_type, params := task.GetNextTarget(next) + + return start, length, target_type, params, nil +} + +func (devices *DeviceSetDM) setTransactionId(oldId uint64, newId uint64) error { + task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) + if task == nil { + return err + } + + err = task.SetSector(0) + if err != nil { + return fmt.Errorf("Can't set sector") + } + + message := fmt.Sprintf("set_transaction_id %d %d", oldId, newId) + err = task.SetMessage(message) + if err != nil { + return fmt.Errorf("Can't set message") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running setTransactionId") + } + return nil +} + +func (devices *DeviceSetDM) hasImage(name string) bool { + dirname := devices.loopbackDir() + filename := path.Join(dirname, name) + + _, err := os.Stat(filename) + return err == nil +} + +func (devices *DeviceSetDM) ensureImage(name string, size int64) (string, error) { + dirname := devices.loopbackDir() + filename := path.Join(dirname, name) + + if err := os.MkdirAll(dirname, 0700); err != nil && !os.IsExist(err) { + return "", err + } + + _, err := os.Stat(filename) + if err != nil { + if !os.IsNotExist(err) { + return "", err + } + log.Printf("Creating loopback file %s for device-manage use", filename) + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + return "", err + } + err = file.Truncate(size) + if err != nil { + return "", err + } + } + return filename, nil +} + +func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) error { + log.Printf("Activating device-mapper pool %s", devices.getPoolName()) + task, err := devices.createTask(DeviceCreate, devices.getPoolName()) + 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() + " 512 8192" + err = task.AddTarget(0, size/512, "thin-pool", params) + if err != nil { + return fmt.Errorf("Can't add target") + } + + var cookie uint32 = 0 + err = task.SetCookie(&cookie, 32) + if err != nil { + return fmt.Errorf("Can't set cookie") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running DeviceCreate") + } + + UdevWait(cookie) + + return nil +} + +func (devices *DeviceSetDM) suspendDevice(info *DevInfo) error { + task, err := devices.createTask(DeviceSuspend, info.Name()) + if task == nil { + return err + } + err = task.Run() + if err != nil { + return fmt.Errorf("Error running DeviceSuspend") + } + return nil +} + +func (devices *DeviceSetDM) resumeDevice(info *DevInfo) error { + task, err := devices.createTask(DeviceResume, info.Name()) + if task == nil { + return err + } + + var cookie uint32 = 0 + err = task.SetCookie(&cookie, 32) + if err != nil { + return fmt.Errorf("Can't set cookie") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running DeviceSuspend") + } + + UdevWait(cookie) + + return nil +} + +func (devices *DeviceSetDM) createDevice(deviceId int) error { + task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) + if task == nil { + return err + } + + err = task.SetSector(0) + if err != nil { + return fmt.Errorf("Can't set sector") + } + + message := fmt.Sprintf("create_thin %d", deviceId) + err = task.SetMessage(message) + if err != nil { + return fmt.Errorf("Can't set message") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running createDevice") + } + return nil +} + +func (devices *DeviceSetDM) createSnapDevice(deviceId int, baseInfo *DevInfo) error { + doSuspend := false + devinfo, _ := devices.getInfo(baseInfo.Name()) + if devinfo != nil && devinfo.Exists != 0 { + doSuspend = true + } + + if doSuspend { + err := devices.suspendDevice(baseInfo) + if err != nil { + return err + } + } + + task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) + if task == nil { + _ = devices.resumeDevice(baseInfo) + return err + } + err = task.SetSector(0) + if err != nil { + _ = devices.resumeDevice(baseInfo) + return fmt.Errorf("Can't set sector") + } + + message := fmt.Sprintf("create_snap %d %d", deviceId, baseInfo.DeviceId) + err = task.SetMessage(message) + if err != nil { + _ = devices.resumeDevice(baseInfo) + return fmt.Errorf("Can't set message") + } + + err = task.Run() + if err != nil { + _ = devices.resumeDevice(baseInfo) + return fmt.Errorf("Error running DeviceCreate") + } + + if doSuspend { + err = devices.resumeDevice(baseInfo) + if err != nil { + return err + } + } + + return nil +} + +func (devices *DeviceSetDM) deleteDevice(deviceId int) error { + task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) + if task == nil { + return err + } + + err = task.SetSector(0) + if err != nil { + return fmt.Errorf("Can't set sector") + } + + message := fmt.Sprintf("delete %d", deviceId) + err = task.SetMessage(message) + if err != nil { + return fmt.Errorf("Can't set message") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running deleteDevice") + } + return nil +} + +func (devices *DeviceSetDM) removeDevice(info *DevInfo) error { + task, err := devices.createTask(DeviceRemove, info.Name()) + if task == nil { + return err + } + err = task.Run() + if err != nil { + return fmt.Errorf("Error running removeDevice") + } + return nil +} + +func (devices *DeviceSetDM) activateDevice(info *DevInfo) error { + task, err := devices.createTask(DeviceCreate, info.Name()) + if task == nil { + return err + } + + params := fmt.Sprintf("%s %d", devices.getPoolDevName(), info.DeviceId) + err = task.AddTarget(0, info.Size/512, "thin", params) + if err != nil { + return fmt.Errorf("Can't add target") + } + + var cookie uint32 = 0 + err = task.SetCookie(&cookie, 32) + if err != nil { + return fmt.Errorf("Can't set cookie") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running DeviceCreate") + } + + UdevWait(cookie) + + return nil +} + +func (devices *DeviceSetDM) allocateDeviceId() int { + // TODO: Add smarter reuse of deleted devices + id := devices.nextFreeDevice + devices.nextFreeDevice = devices.nextFreeDevice + 1 + return id +} + +func (devices *DeviceSetDM) allocateTransactionId() uint64 { + devices.NewTransactionId = devices.NewTransactionId + 1 + return devices.NewTransactionId +} + +func (devices *DeviceSetDM) saveMetadata() error { + jsonData, err := json.Marshal(devices.MetaData) + if err != nil { + return err + } + tmpFile, err := ioutil.TempFile(filepath.Dir(devices.jsonFile()), ".json") + if err != nil { + return err + } + + n, err := tmpFile.Write(jsonData) + if err != nil { + return err + } + if n < len(jsonData) { + err = io.ErrShortWrite + } + err = tmpFile.Sync() + if err != nil { + return err + } + err = tmpFile.Close() + if err != nil { + return err + } + err = os.Rename(tmpFile.Name(), devices.jsonFile()) + if err != nil { + return err + } + + if devices.NewTransactionId != devices.TransactionId { + err = devices.setTransactionId(devices.TransactionId, devices.NewTransactionId) + if err != nil { + return err + } + devices.TransactionId = devices.NewTransactionId + } + + return nil +} + +func (devices *DeviceSetDM) registerDevice(id int, hash string, size uint64) (*DevInfo, error) { + transaction := devices.allocateTransactionId() + + info := &DevInfo{ + Hash: hash, + DeviceId: id, + Size: size, + TransactionId: transaction, + Initialized: false, + } + + devices.Devices[hash] = info + err := devices.saveMetadata() + if err != nil { + // Try to remove unused device + devices.Devices[hash] = nil + return nil, err + } + + return info, nil +} + +func (devices *DeviceSetDM) activateDeviceIfNeeded(hash string) error { + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("Unknown device %s", hash) + } + + name := info.Name() + devinfo, _ := devices.getInfo(name) + if devinfo != nil && devinfo.Exists != 0 { + return nil + } + + return devices.activateDevice(info) +} + +func (devices *DeviceSetDM) createFilesystem(info *DevInfo) error { + devname := info.DevName() + + err := exec.Command("mkfs.ext4", "-E", + "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run() + if err != nil { + err = exec.Command("mkfs.ext4", "-E", + "discard,lazy_itable_init=0", devname).Run() + } + if err != nil { + return err + } + return nil +} + +func (devices *DeviceSetDM) loadMetaData() error { + _, _, _, params, err := devices.getStatus(devices.getPoolName()) + if err != nil { + return err + } + var currentTransaction uint64 + _, err = fmt.Sscanf(params, "%d", ¤tTransaction) + if err != nil { + return err + } + + devices.TransactionId = currentTransaction + devices.NewTransactionId = devices.TransactionId + + jsonData, err := ioutil.ReadFile(devices.jsonFile()) + if err != nil && !os.IsNotExist(err) { + return err + } + + metadata := &MetaData{ + Devices: make(map[string]*DevInfo), + } + if jsonData != nil { + if err := json.Unmarshal(jsonData, metadata); err != nil { + return err + } + } + devices.MetaData = *metadata + + for hash, d := range devices.Devices { + d.Hash = hash + + 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 > currentTransaction { + log.Printf("Removing lost device %s with id %d", hash, d.TransactionId) + delete(devices.Devices, hash) + } + } + + return nil +} + +func (devices *DeviceSetDM) createBaseLayer(dir string) error { + for pth, typ := range map[string]string{ + "/dev/pts": "dir", + "/dev/shm": "dir", + "/proc": "dir", + "/sys": "dir", + "/.dockerinit": "file", + "/etc/resolv.conf": "file", + "/etc/hosts": "file", + "/etc/hostname": "file", + // "var/run": "dir", + // "var/lock": "dir", + } { + if _, err := os.Stat(path.Join(dir, pth)); err != nil { + if os.IsNotExist(err) { + switch typ { + case "dir": + if err := os.MkdirAll(path.Join(dir, pth), 0755); err != nil { + return err + } + case "file": + if err := os.MkdirAll(path.Join(dir, path.Dir(pth)), 0755); err != nil { + return err + } + + if f, err := os.OpenFile(path.Join(dir, pth), os.O_CREATE, 0755); err != nil { + return err + } else { + f.Close() + } + } + } else { + return err + } + } + } + return nil +} + +func (devices *DeviceSetDM) setupBaseImage() error { + oldInfo := devices.Devices[""] + if oldInfo != nil && oldInfo.Initialized { + return nil + } + + if oldInfo != nil && !oldInfo.Initialized { + log.Printf("Removing uninitialized base image") + if err := devices.RemoveDevice(""); err != nil { + return err + } + } + + log.Printf("Initializing base device-manager snapshot") + + id := devices.allocateDeviceId() + + // Create initial device + err := devices.createDevice(id) + if err != nil { + return err + } + + info, err := devices.registerDevice(id, "", defaultBaseFsSize) + if err != nil { + _ = devices.deleteDevice(id) + return err + } + + log.Printf("Creating filesystem on base device-manager snapshot") + + err = devices.activateDeviceIfNeeded("") + if err != nil { + return err + } + + err = devices.createFilesystem(info) + if err != nil { + return err + } + + tmpDir := path.Join(devices.loopbackDir(), "basefs") + if err = os.MkdirAll(tmpDir, 0700); err != nil && !os.IsExist(err) { + return err + } + + err = devices.MountDevice("", tmpDir) + if err != nil { + return err + } + + err = devices.createBaseLayer(tmpDir) + if err != nil { + _ = syscall.Unmount(tmpDir, 0) + return err + } + + err = syscall.Unmount(tmpDir, 0) + if err != nil { + return err + } + + _ = os.Remove(tmpDir) + + info.Initialized = true + + err = devices.saveMetadata() + if err != nil { + info.Initialized = false + return err + } + + return nil +} + +func (devices *DeviceSetDM) initDevmapper() error { + info, err := devices.getInfo(devices.getPoolName()) + if info == nil { + return err + } + + if info.Exists != 0 { + /* Pool exists, assume everything is up */ + err = devices.loadMetaData() + if err != nil { + return err + } + err = devices.setupBaseImage() + if err != nil { + return err + } + return nil + } + + createdLoopback := false + if !devices.hasImage("data") || !devices.hasImage("metadata") { + /* If we create the loopback mounts we also need to initialize the base fs */ + createdLoopback = true + } + + data, err := devices.ensureImage("data", defaultDataLoopbackSize) + if err != nil { + return err + } + + metadata, err := devices.ensureImage("metadata", defaultMetaDataLoopbackSize) + if err != nil { + return err + } + + dataFile, err := AttachLoopDevice(data) + if err != nil { + return err + } + defer dataFile.Close() + + metadataFile, err := AttachLoopDevice(metadata) + if err != nil { + return err + } + defer metadataFile.Close() + + err = devices.createPool(dataFile, metadataFile) + if err != nil { + return err + } + + if !createdLoopback { + err = devices.loadMetaData() + if err != nil { + return err + } + } + + err = devices.setupBaseImage() + if err != nil { + return err + } + + return nil +} + +func (devices *DeviceSetDM) AddDevice(hash, baseHash string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + if devices.Devices[hash] != nil { + return fmt.Errorf("hash %s already exists", hash) + } + + baseInfo := devices.Devices[baseHash] + if baseInfo == nil { + return fmt.Errorf("Unknown base hash %s", baseHash) + } + + deviceId := devices.allocateDeviceId() + + err := devices.createSnapDevice(deviceId, baseInfo) + if err != nil { + return err + } + + _, err = devices.registerDevice(deviceId, hash, baseInfo.Size) + if err != nil { + _ = devices.deleteDevice(deviceId) + return err + } + return nil +} + +func (devices *DeviceSetDM) RemoveDevice(hash string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("hash %s doesn't exists", hash) + } + + devinfo, _ := devices.getInfo(info.Name()) + if devinfo != nil && devinfo.Exists != 0 { + err := devices.removeDevice(info) + if err != nil { + return err + } + } + + if info.Initialized { + info.Initialized = false + err := devices.saveMetadata() + if err != nil { + return err + } + } + + err := devices.deleteDevice(info.DeviceId) + if err != nil { + return err + } + + _ = devices.allocateTransactionId() + delete(devices.Devices, info.Hash) + + err = devices.saveMetadata() + if err != nil { + devices.Devices[info.Hash] = info + return err + } + + return nil +} + +func (devices *DeviceSetDM) DeactivateDevice(hash string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("hash %s doesn't exists", hash) + } + + devinfo, err := devices.getInfo(info.Name()) + if err != nil { + return err + } + if devinfo.Exists != 0 { + err := devices.removeDevice(info) + if err != nil { + return err + } + } + + return nil +} + +func (devices *DeviceSetDM) MountDevice(hash, path string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + err := devices.activateDeviceIfNeeded(hash) + if err != nil { + return err + } + + info := devices.Devices[hash] + + err = syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "discard") + if err != nil && err == syscall.EINVAL { + err = syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "") + } + if err != nil { + return err + } + return nil +} + +func (devices *DeviceSetDM) HasDevice(hash string) bool { + if err := devices.ensureInit(); err != nil { + return false + } + + info := devices.Devices[hash] + return info != nil +} + +func (devices *DeviceSetDM) HasInitializedDevice(hash string) bool { + if err := devices.ensureInit(); err != nil { + return false + } + + info := devices.Devices[hash] + return info != nil && info.Initialized +} + +func (devices *DeviceSetDM) SetInitialized(hash string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("Unknown device %s", hash) + } + + info.Initialized = true + err := devices.saveMetadata() + if err != nil { + info.Initialized = false + return err + } + + return nil +} + +func (devices *DeviceSetDM) ensureInit() error { + if (!devices.initialized) { + devices.initialized = true + err := devices.initDevmapper() + if err != nil { + return err + } + } + return nil +} + +func NewDeviceSetDM(root string) *DeviceSetDM { + SetDevDir("/dev") + devices := &DeviceSetDM{ + initialized: false, + root: root, + } + devices.Devices = make(map[string]*DevInfo) + + return devices +} From 2b1dc8a8a3b99e6edbdf2cc71bf5461d81b9c354 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:25:32 +0200 Subject: [PATCH 003/126] devmapper: Add simple tool to test the DeviceSet commands --- devmapper/docker-device-tool/device_tool.go | 62 +++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 devmapper/docker-device-tool/device_tool.go diff --git a/devmapper/docker-device-tool/device_tool.go b/devmapper/docker-device-tool/device_tool.go new file mode 100644 index 0000000000..28bdf56074 --- /dev/null +++ b/devmapper/docker-device-tool/device_tool.go @@ -0,0 +1,62 @@ +package main + +import ( + "fmt" + "github.com/dotcloud/docker/devmapper" + "os" +) + +func usage() { + fmt.Printf("Usage: %s [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0]) + os.Exit(1) +} + +func main() { + devices := devmapper.NewDeviceSetDM("/var/lib/docker") + + if len(os.Args) < 2 { + usage() + } + + cmd := os.Args[1] + if cmd == "snap" { + if len(os.Args) < 4 { + usage() + } + + err := devices.AddDevice(os.Args[2], os.Args[3]) + if err != nil { + fmt.Println("Can't create snap device: ", err) + os.Exit(1) + } + } else if cmd == "remove" { + if len(os.Args) < 3 { + usage() + } + + err := devices.RemoveDevice(os.Args[2]) + if err != nil { + fmt.Println("Can't remove device: ", err) + os.Exit(1) + } + } else if cmd == "mount" { + if len(os.Args) < 4 { + usage() + } + + err := devices.MountDevice(os.Args[2], os.Args[3]) + if err != nil { + fmt.Println("Can't create snap device: ", err) + os.Exit(1) + } + } else { + fmt.Printf("Unknown command %s\n", cmd) + if len(os.Args) < 4 { + usage() + } + + os.Exit(1) + } + + return +} From 250bc3f61547c6bcdc17ffe41e8a9a0307099cc3 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 10 Sep 2013 20:15:23 +0200 Subject: [PATCH 004/126] Add a separate docker-init binary This may be used for the .dockerinit case if the main binary is not statically linked. --- docker-init/docker-init.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 docker-init/docker-init.go diff --git a/docker-init/docker-init.go b/docker-init/docker-init.go new file mode 100644 index 0000000000..c368c53138 --- /dev/null +++ b/docker-init/docker-init.go @@ -0,0 +1,16 @@ +package main + +import ( + "github.com/dotcloud/docker" +) + +var ( + GITCOMMIT string + VERSION string +) + +func main() { + // Running in init mode + docker.SysInit() + return +} From 0f5ccf934e01c75e467e6f35e883cf95bae74f2c Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 10 Sep 2013 20:21:15 +0200 Subject: [PATCH 005/126] Runtime: Automatically use docker-init if it exists In some builds the main docker binary is not statically linked, and as such not usable in as the .dockerinit binary, for those cases we look for a separately shipped docker-init binary and use that instead. --- runtime.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/runtime.go b/runtime.go index aff1773fdf..f8dc48562a 100644 --- a/runtime.go +++ b/runtime.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path" + "path/filepath" "sort" "strings" "time" @@ -42,7 +43,17 @@ type Runtime struct { var sysInitPath string func init() { - sysInitPath = utils.SelfPath() + selfPath := utils.SelfPath() + + // If we have a separate docker-init, use that, otherwise use the + // main docker binary + dir := filepath.Dir(selfPath) + dockerInitPath := filepath.Join(dir, "docker-init") + if _, err := os.Stat(dockerInitPath); err != nil { + sysInitPath = selfPath + } else { + sysInitPath = dockerInitPath + } } // List returns an array of all containers registered in the runtime. From 8637ba710e8bd10ee36aa5c71f5ef54ab9037dfa Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:41:38 +0200 Subject: [PATCH 006/126] Image: Add runtime and container id args to Mount() We will later need the runtime to get access to the VolumeSet singleton, and the container id to have a name for the volume for the container --- container.go | 2 +- graph_test.go | 5 ++++- image.go | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/container.go b/container.go index 2616fb221f..54451baad6 100644 --- a/container.go +++ b/container.go @@ -1157,7 +1157,7 @@ func (container *Container) Mount() error { if err != nil { return err } - return image.Mount(container.RootfsPath(), container.rwPath()) + return image.Mount(container.runtime, container.RootfsPath(), container.rwPath(), container.ID) } func (container *Container) Changes() ([]Change, error) { diff --git a/graph_test.go b/graph_test.go index 471016938d..d89e6efe9d 100644 --- a/graph_test.go +++ b/graph_test.go @@ -121,6 +121,9 @@ func TestRegister(t *testing.T) { } func TestMount(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + graph := tempGraph(t) defer os.RemoveAll(graph.Root) archive, err := fakeTar() @@ -144,7 +147,7 @@ func TestMount(t *testing.T) { if err := os.MkdirAll(rw, 0700); err != nil { t.Fatal(err) } - if err := image.Mount(rootfs, rw); err != nil { + if err := image.Mount(runtime, rootfs, rw, "testing"); err != nil { t.Fatal(err) } // FIXME: test for mount contents diff --git a/image.go b/image.go index 9f34160b80..bbf559e23a 100644 --- a/image.go +++ b/image.go @@ -170,7 +170,7 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) { return Tar(layerPath, compression) } -func (image *Image) Mount(root, rw string) error { +func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { if mounted, err := Mounted(root); err != nil { return err } else if mounted { From ac194fc696fb95045ee5b634d04a9f7093f45685 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:44:11 +0200 Subject: [PATCH 007/126] Add DeviceSet interface This interface matches the device-mapper implementation (DeviceSetDM) but is free from any dependencies. This allows core docker code to refer to a DeviceSet without having an explicit dependency on the devmapper package. This is important, because the devmapper package has external dependencies which are not wanted in the docker client app, as it needs to run with minimal dependencies in the docker image. --- deviceset.go | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 deviceset.go diff --git a/deviceset.go b/deviceset.go new file mode 100644 index 0000000000..01bdba411f --- /dev/null +++ b/deviceset.go @@ -0,0 +1,11 @@ +package docker + +type DeviceSet interface { + AddDevice(hash, baseHash string) error + SetInitialized(hash string) error + DeactivateDevice(hash string) error + RemoveDevice(hash string) error + MountDevice(hash, path string) error + HasDevice(hash string) bool + HasInitializedDevice(hash string) bool +} From 87e248f52458eca2a40ed6e54e36de89228f6790 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 12:03:45 +0200 Subject: [PATCH 008/126] Server: Pass in device-mapper DeviceSet to server This makes docker (but not docker-init) link to libdevmapper and will allow it to use the DeviceSet --- docker/docker.go | 3 ++- server.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docker/docker.go b/docker/docker.go index a0021f3a87..4dcc174d5e 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "github.com/dotcloud/docker" + "github.com/dotcloud/docker/devmapper" "github.com/dotcloud/docker/utils" "io/ioutil" "log" @@ -133,7 +134,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart if flDns != "" { dns = []string{flDns} } - server, err := docker.NewServer(flGraphPath, autoRestart, enableCors, dns) + server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns) if err != nil { return err } diff --git a/server.go b/server.go index 88c32d17e1..295b7d80b9 100644 --- a/server.go +++ b/server.go @@ -1294,7 +1294,7 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er } -func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { +func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } From f317a6b6fe31685445ac97a1475136c5ab7860b5 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 14:40:12 +0200 Subject: [PATCH 009/126] Runtime: Add DeviceSet singleton This adds a DeviceSet singleton to the Runtime object which will be used for any DeviceMapper dependent code. --- runtime.go | 15 ++++++++++++--- runtime_test.go | 5 +++-- server.go | 2 +- utils_test.go | 3 ++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/runtime.go b/runtime.go index f8dc48562a..2ae7c03a03 100644 --- a/runtime.go +++ b/runtime.go @@ -38,6 +38,7 @@ type Runtime struct { volumes *Graph srv *Server Dns []string + deviceSet DeviceSet } var sysInitPath string @@ -75,6 +76,13 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element { return nil } +func (runtime *Runtime) GetDeviceSet() (DeviceSet, error) { + if runtime.deviceSet == nil { + return nil, fmt.Errorf("No device set available") + } + return runtime.deviceSet, nil +} + // Get looks for a container by the specified ID or name, and returns it. // If the container is not found, or if an error occurs, nil is returned. func (runtime *Runtime) Get(name string) *Container { @@ -438,8 +446,8 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a } // FIXME: harmonize with NewGraph() -func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, error) { - runtime, err := NewRuntimeFromDirectory(flGraphPath, autoRestart) +func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string) (*Runtime, error) { + runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart) if err != nil { return nil, err } @@ -457,7 +465,7 @@ func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, e return runtime, nil } -func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { +func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) (*Runtime, error) { runtimeRepo := path.Join(root, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { @@ -494,6 +502,7 @@ func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { capabilities: &Capabilities{}, autoRestart: autoRestart, volumes: volumes, + deviceSet: deviceSet, } if err := runtime.restore(); err != nil { diff --git a/runtime_test.go b/runtime_test.go index f4f5d5af1e..503f519d12 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/devmapper" "io" "log" "net" @@ -87,7 +88,7 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreBase), false); err != nil { panic(err) } else { globalRuntime = runtime @@ -456,7 +457,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, false) + runtime2, err := NewRuntimeFromDirectory(runtime1.root, devmapper.NewDeviceSetDM(runtime1.root), false) if err != nil { t.Fatal(err) } diff --git a/server.go b/server.go index 295b7d80b9..1b4c0790ae 100644 --- a/server.go +++ b/server.go @@ -1298,7 +1298,7 @@ func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } - runtime, err := NewRuntime(flGraphPath, autoRestart, dns) + runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns) if err != nil { return nil, err } diff --git a/utils_test.go b/utils_test.go index 740a5fc1bc..bc4dd65a3c 100644 --- a/utils_test.go +++ b/utils_test.go @@ -2,6 +2,7 @@ package docker import ( "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/devmapper" "io" "io/ioutil" "os" @@ -42,7 +43,7 @@ func newTestRuntime() (*Runtime, error) { return nil, err } - runtime, err := NewRuntimeFromDirectory(root, false) + runtime, err := NewRuntimeFromDirectory(root, devmapper.NewDeviceSetDM(root), false) if err != nil { return nil, err } From 53851474c0b8127442ce11ab38fa0ae8d5c694f0 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 10 Sep 2013 22:15:18 +0200 Subject: [PATCH 010/126] Runtime: Add MountMethod to allow AUFS and device-mapper to coexist --- runtime.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/runtime.go b/runtime.go index 2ae7c03a03..1a89340075 100644 --- a/runtime.go +++ b/runtime.go @@ -17,6 +17,13 @@ import ( ) var defaultDns = []string{"8.8.8.8", "8.8.4.4"} +type MountMethod int + +const ( + MountMethodNone MountMethod = iota + MountMethodAUFS + MountMethodDeviceMapper +) type Capabilities struct { MemoryLimit bool @@ -39,6 +46,7 @@ type Runtime struct { srv *Server Dns []string deviceSet DeviceSet + mountMethod MountMethod } var sysInitPath string @@ -76,6 +84,46 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element { return nil } +func hasFilesystemSupport(fstype string) bool { + content, err := ioutil.ReadFile("/proc/filesystems") + if err != nil { + log.Printf("WARNING: Unable to read /proc/filesystems, assuming fs %s is not supported.", fstype) + return false + } + lines := strings.Split(string(content), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "nodev") { + line = line[5:] + } + line = strings.TrimSpace(line) + if line == fstype { + return true + } + } + return false +} + +func (runtime *Runtime) GetMountMethod() MountMethod { + if runtime.mountMethod == MountMethodNone { + // Try to automatically pick a method + if hasFilesystemSupport("aufs") { + log.Printf("Using AUFS backend.") + runtime.mountMethod = MountMethodAUFS + } else { + _ = exec.Command("modprobe", "aufs").Run() + if hasFilesystemSupport("aufs") { + log.Printf("Using AUFS backend.") + runtime.mountMethod = MountMethodAUFS + } else { + log.Printf("Using device-mapper backend.") + runtime.mountMethod = MountMethodDeviceMapper + } + } + } + + return runtime.mountMethod +} + func (runtime *Runtime) GetDeviceSet() (DeviceSet, error) { if runtime.deviceSet == nil { return nil, fmt.Errorf("No device set available") From fcd41fe51ae0418d583f3d33dfac7fc0879ca30e Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 12:55:48 +0200 Subject: [PATCH 011/126] Image: Initial support for device-mapper mounts This supports creating images from layers and mounting them for running a container. Not supported yet are: * Creating diffs between images/containers * Creating layers for new images from a device-mapper container --- image.go | 269 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 260 insertions(+), 9 deletions(-) diff --git a/image.go b/image.go index bbf559e23a..07eca11269 100644 --- a/image.go +++ b/image.go @@ -15,6 +15,7 @@ import ( "path/filepath" "strconv" "strings" + "syscall" "time" ) @@ -136,6 +137,10 @@ func jsonPath(root string) string { return path.Join(root, "json") } +func mountPath(root string) string { + return path.Join(root, "mount") +} + func MountAUFS(ro []string, rw string, target string) error { // FIXME: Now mount the layers rwBranch := fmt.Sprintf("%v=rw", rw) @@ -170,25 +175,271 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) { return Tar(layerPath, compression) } +func (image *Image) applyLayer(layer, target string) error { + oldmask := syscall.Umask(0) + defer syscall.Umask(oldmask) + err := filepath.Walk(layer, func(srcPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + // Skip root + if srcPath == layer { + return nil + } + + var srcStat syscall.Stat_t + err = syscall.Lstat(srcPath, &srcStat) + if err != nil { + return err + } + + relPath, err := filepath.Rel(layer, srcPath) + if err != nil { + return err + } + + targetPath := filepath.Join(target, relPath) + + // Skip AUFS metadata + if matched, err := filepath.Match(".wh..wh.*", relPath); err != nil || matched { + if err != nil || !f.IsDir() { + return err + } + return filepath.SkipDir + } + + // Find out what kind of modification happened + file := filepath.Base(srcPath) + + // If there is a whiteout, then the file was removed + if strings.HasPrefix(file, ".wh.") { + originalFile := file[len(".wh."):] + deletePath := filepath.Join(filepath.Dir(targetPath), originalFile) + + err = os.RemoveAll(deletePath) + if err != nil { + return err + } + } else { + var targetStat = &syscall.Stat_t{} + err := syscall.Lstat(targetPath, targetStat) + if err != nil { + if !os.IsNotExist(err) { + return err + } + targetStat = nil + } + + if targetStat != nil && !(targetStat.Mode&syscall.S_IFDIR == syscall.S_IFDIR && srcStat.Mode&syscall.S_IFDIR == syscall.S_IFDIR) { + // Unless both src and dest are directories we remove the target and recreate it + // This is a bit wasteful in the case of only a mode change, but that is unlikely + // to matter much + err = os.RemoveAll(targetPath) + if err != nil { + return err + } + targetStat = nil + } + + if f.IsDir() { + // Source is a directory + if targetStat == nil { + err = syscall.Mkdir(targetPath, srcStat.Mode&07777) + if err != nil { + return err + } + } else if srcStat.Mode&07777 != targetStat.Mode&07777 { + err = syscall.Chmod(targetPath, srcStat.Mode&07777) + if err != nil { + return err + } + } + } else if srcStat.Mode&syscall.S_IFLNK == syscall.S_IFLNK { + // Source is symlink + link, err := os.Readlink(srcPath) + if err != nil { + return err + } + + err = os.Symlink(link, targetPath) + if err != nil { + return err + } + } else if srcStat.Mode&syscall.S_IFBLK == syscall.S_IFBLK || + srcStat.Mode&syscall.S_IFCHR == syscall.S_IFCHR || + srcStat.Mode&syscall.S_IFIFO == syscall.S_IFIFO || + srcStat.Mode&syscall.S_IFSOCK == syscall.S_IFSOCK { + // Source is special file + err = syscall.Mknod(targetPath, srcStat.Mode, int(srcStat.Rdev)) + if err != nil { + return err + } + } else if srcStat.Mode&syscall.S_IFREG == syscall.S_IFREG { + // Source is regular file + fd, err := syscall.Open(targetPath, syscall.O_CREAT|syscall.O_WRONLY, srcStat.Mode&07777) + if err != nil { + return err + } + dstFile := os.NewFile(uintptr(fd), targetPath) + srcFile, err := os.Open(srcPath) + _, err = io.Copy(dstFile, srcFile) + if err != nil { + return err + } + _ = srcFile.Close() + _ = dstFile.Close() + } else { + return fmt.Errorf("Unknown type for file %s", srcPath) + } + + if srcStat.Mode&syscall.S_IFLNK != syscall.S_IFLNK { + err = syscall.Chown(targetPath, int(srcStat.Uid), int(srcStat.Gid)) + if err != nil { + return err + } + ts := []syscall.Timeval{ + syscall.NsecToTimeval(srcStat.Atim.Nano()), + syscall.NsecToTimeval(srcStat.Mtim.Nano()), + } + syscall.Utimes(targetPath, ts) + } + + } + return nil + }) + return err +} + +func (image *Image) ensureImageDevice(devices DeviceSet) error { + if devices.HasInitializedDevice(image.ID) { + return nil + } + + if image.Parent != "" && !devices.HasInitializedDevice(image.Parent) { + parentImg, err := image.GetParent() + if err != nil { + return fmt.Errorf("Error while getting parent image: %v", err) + } + err = parentImg.ensureImageDevice(devices) + if err != nil { + return err + } + } + + root, err := image.root() + if err != nil { + return err + } + + mountDir := mountPath(root) + if err := os.Mkdir(mountDir, 0600); err != nil && !os.IsExist(err) { + return err + } + + mounted, err := Mounted(mountDir) + if err == nil && mounted { + log.Printf("Image %s is unexpectedly mounted, unmounting...", image.ID) + err = syscall.Unmount(mountDir, 0) + if err != nil { + return err + } + } + + if devices.HasDevice(image.ID) { + log.Printf("Found non-initialized demove-mapper device for image %s, removing", image.ID) + err = devices.RemoveDevice(image.ID) + if err != nil { + return err + } + } + + log.Printf("Creating device-mapper device for image id %s", image.ID) + + err = devices.AddDevice(image.ID, image.Parent) + if err != nil { + return err + } + + utils.Debugf("Mounting device %s at %s for image setup", image.ID, mountDir) + err = devices.MountDevice(image.ID, mountDir) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + + utils.Debugf("Applying layer %s at %s", image.ID, mountDir) + err = image.applyLayer(layerPath(root), mountDir) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + + utils.Debugf("Unmounting %s", mountDir) + err = syscall.Unmount(mountDir, 0) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + + devices.SetInitialized(image.ID) + + // No need to the device-mapper device to hang around once we've written + // the image, it can be enabled on-demand when needed + devices.DeactivateDevice(image.ID) + + return nil +} + func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { if mounted, err := Mounted(root); err != nil { return err } else if mounted { return fmt.Errorf("%s is already mounted", root) } - layers, err := image.layers() - if err != nil { - return err - } // Create the target directories if they don't exist if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) { return err } - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return err - } - if err := MountAUFS(layers, rw, root); err != nil { - return err + switch runtime.GetMountMethod() { + case MountMethodNone: + return fmt.Errorf("No supported Mount implementation") + + case MountMethodAUFS: + if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { + return err + } + layers, err := image.layers() + if err != nil { + return err + } + if err := MountAUFS(layers, rw, root); err != nil { + return err + } + + case MountMethodDeviceMapper: + devices, err := runtime.GetDeviceSet() + if err != nil { + return err + } + err = image.ensureImageDevice(devices) + if err != nil { + return err + } + + if !devices.HasDevice(id) { + utils.Debugf("Creating device %s for container based on image %s", id, image.ID) + err = devices.AddDevice(id, image.ID) + if err != nil { + return err + } + } + + utils.Debugf("Mounting container %s at %s for container", id, root) + err = devices.MountDevice(id, root) + if err != nil { + return err + } } return nil } From a89a51128ecdf2db02a333a406752416de8a1db6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 14:21:53 +0200 Subject: [PATCH 012/126] Image: Deactivate image device when unmounting container There is no need to keep all the device-mapper devices active, we can just activate them on demand if needed. --- container.go | 7 ++++++- image.go | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/container.go b/container.go index 54451baad6..9b4412c6ba 100644 --- a/container.go +++ b/container.go @@ -1180,7 +1180,12 @@ func (container *Container) Mounted() (bool, error) { } func (container *Container) Unmount() error { - return Unmount(container.RootfsPath()) + image, err := container.GetImage() + if err != nil { + return err + } + err = image.Unmount(container.runtime, container.RootfsPath(), container.ID) + return err } // ShortID returns a shorthand version of the container's id for convenience. diff --git a/image.go b/image.go index 07eca11269..b306dfd956 100644 --- a/image.go +++ b/image.go @@ -444,6 +444,30 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { return nil } +func (image *Image) Unmount(runtime *Runtime, root string, id string) error { + switch runtime.GetMountMethod() { + case MountMethodNone: + return fmt.Errorf("No supported Unmount implementation") + + case MountMethodAUFS: + return Unmount(root) + + case MountMethodDeviceMapper: + err := syscall.Unmount(root, 0) + if err != nil { + return err + } + + // Try to deactivate the device as generally there is no use for it anymore + devices, err := runtime.GetDeviceSet() + if err != nil { + return err; + } + return devices.DeactivateDevice(id) + } + return nil +} + func (image *Image) Changes(rw string) ([]Change, error) { layers, err := image.layers() if err != nil { From b125f2334c7b68f624ee1eee06cb9d68922d0314 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 15:44:01 +0200 Subject: [PATCH 013/126] Image: Always create a .docker-id file in the devices we create Without this there is really no way to map back from the device-mapper devices to the actual docker image/container ids in case the json file somehow got lost --- image.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/image.go b/image.go index b306dfd956..9d9a64cceb 100644 --- a/image.go +++ b/image.go @@ -368,6 +368,13 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } + + err = ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + utils.Debugf("Applying layer %s at %s", image.ID, mountDir) err = image.applyLayer(layerPath(root), mountDir) if err != nil { @@ -427,12 +434,14 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { return err } + createdDevice := false if !devices.HasDevice(id) { utils.Debugf("Creating device %s for container based on image %s", id, image.ID) err = devices.AddDevice(id, image.ID) if err != nil { return err } + createdDevice = true } utils.Debugf("Mounting container %s at %s for container", id, root) @@ -440,6 +449,15 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { if err != nil { return err } + + if createdDevice { + err = ioutil.WriteFile(path.Join(root, ".docker-id"), []byte(id), 0600) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + } + } return nil } From 8f343ea65a05b374ffa64940a1946abf28402689 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 15:32:57 +0200 Subject: [PATCH 014/126] devmapper: Base the device-mapper names on the root dir name This means the default is "docker-*", but for tests we get separate prefixes for each test. --- devmapper/deviceset_devmapper.go | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go index d7e122cf28..4544215b63 100644 --- a/devmapper/deviceset_devmapper.go +++ b/devmapper/deviceset_devmapper.go @@ -10,6 +10,7 @@ import ( "os/exec" "path" "path/filepath" + "strings" "syscall" ) @@ -18,11 +19,12 @@ const defaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 const 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"` + Hash string `json:"-"` + DeviceId int `json:"device_id"` + Size uint64 `json:"size"` + TransactionId uint64 `json:"transaction_id"` + Initialized bool `json:"initialized"` + devices *DeviceSetDM `json:"-"` } type MetaData struct { @@ -31,7 +33,8 @@ type MetaData struct { type DeviceSetDM struct { initialized bool - root string + root string + devicePrefix string MetaData TransactionId uint64 NewTransactionId uint64 @@ -47,7 +50,7 @@ func (info *DevInfo) Name() string { if hash == "" { hash = "base" } - return fmt.Sprintf("docker-%s", hash) + return fmt.Sprintf("%s-%s", info.devices.devicePrefix, hash) } func (info *DevInfo) DevName() string { @@ -63,7 +66,7 @@ func (devices *DeviceSetDM) jsonFile() string { } func (devices *DeviceSetDM) getPoolName() string { - return "docker-pool" + return fmt.Sprintf("%s-pool", devices.devicePrefix) } func (devices *DeviceSetDM) getPoolDevName() string { @@ -446,6 +449,7 @@ func (devices *DeviceSetDM) registerDevice(id int, hash string, size uint64) (*D Size: size, TransactionId: transaction, Initialized: false, + devices: devices, } devices.Devices[hash] = info @@ -520,6 +524,7 @@ func (devices *DeviceSetDM) loadMetaData() error { for hash, d := range devices.Devices { d.Hash = hash + d.devices = devices if d.DeviceId >= devices.nextFreeDevice { devices.nextFreeDevice = d.DeviceId + 1 @@ -873,7 +878,7 @@ func (devices *DeviceSetDM) SetInitialized(hash string) error { } func (devices *DeviceSetDM) ensureInit() error { - if (!devices.initialized) { + if !devices.initialized { devices.initialized = true err := devices.initDevmapper() if err != nil { @@ -885,9 +890,16 @@ func (devices *DeviceSetDM) ensureInit() error { func NewDeviceSetDM(root string) *DeviceSetDM { SetDevDir("/dev") + + base := filepath.Base(root) + if !strings.HasPrefix(base, "docker") { + base = "docker-" + base + } + devices := &DeviceSetDM{ initialized: false, - root: root, + root: root, + devicePrefix: base, } devices.Devices = make(map[string]*DevInfo) From 8e8ef7cb5b208ac657af812ebc5ffa783664cf3b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 18:03:49 +0200 Subject: [PATCH 015/126] Implement docker diff for device-mapper To do diffing we just compare file metadata, so this relies on things like size and mtime/ctime to catch any changes. Its *possible* to trick this by updating a file without changing the size and setting back the mtime/ctime, but that seems pretty unlikely to happen in reality, and lets us avoid comparing the actual file data. --- changes.go | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++- container.go | 6 ++- image.go | 39 ++++++++++++++++--- 3 files changed, 142 insertions(+), 7 deletions(-) diff --git a/changes.go b/changes.go index 43573cd606..00c9cc7c77 100644 --- a/changes.go +++ b/changes.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "strings" + "syscall" ) type ChangeType int @@ -33,7 +34,7 @@ func (change *Change) String() string { return fmt.Sprintf("%s %s", kind, change.Path) } -func Changes(layers []string, rw string) ([]Change, error) { +func ChangesAUFS(layers []string, rw string) ([]Change, error) { var changes []Change err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error { if err != nil { @@ -104,3 +105,104 @@ func Changes(layers []string, rw string) ([]Change, error) { } return changes, nil } + +func ChangesDirs(newDir, oldDir string) ([]Change, error) { + var changes []Change + err := filepath.Walk(newDir, func(newPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + var newStat syscall.Stat_t + err = syscall.Lstat(newPath, &newStat) + if err != nil { + return err + } + + // Rebase path + relPath, err := filepath.Rel(newDir, newPath) + if err != nil { + return err + } + relPath = filepath.Join("/", relPath) + + // Skip root + if relPath == "/" || relPath == "/.docker-id" { + return nil + } + + change := Change{ + Path: relPath, + } + + oldPath := filepath.Join(oldDir, relPath) + + var oldStat = &syscall.Stat_t{} + err = syscall.Lstat(oldPath, oldStat) + if err != nil { + if !os.IsNotExist(err) { + return err + } + oldStat = nil + } + + if oldStat == nil { + change.Kind = ChangeAdd + changes = append(changes, change) + } else { + if oldStat.Ino != newStat.Ino || + oldStat.Mode != newStat.Mode || + oldStat.Uid != newStat.Uid || + oldStat.Gid != newStat.Gid || + oldStat.Rdev != newStat.Rdev || + oldStat.Size != newStat.Size || + oldStat.Blocks != newStat.Blocks || + oldStat.Mtim != newStat.Mtim || + oldStat.Ctim != newStat.Ctim { + change.Kind = ChangeModify + changes = append(changes, change) + } + } + + return nil + }) + if err != nil { + return nil, err + } + err = filepath.Walk(oldDir, func(oldPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + // Rebase path + relPath, err := filepath.Rel(oldDir, oldPath) + if err != nil { + return err + } + relPath = filepath.Join("/", relPath) + + // Skip root + if relPath == "/" { + return nil + } + + change := Change{ + Path: relPath, + } + + newPath := filepath.Join(newDir, relPath) + + var newStat = &syscall.Stat_t{} + err = syscall.Lstat(newPath, newStat) + if err != nil && os.IsNotExist(err) { + change.Kind = ChangeDelete + changes = append(changes, change) + } + + return nil + }) + if err != nil { + return nil, err + } + return changes, nil +} diff --git a/container.go b/container.go index 9b4412c6ba..33b410ee5a 100644 --- a/container.go +++ b/container.go @@ -1161,11 +1161,15 @@ func (container *Container) Mount() error { } func (container *Container) Changes() ([]Change, error) { + if err := container.EnsureMounted(); err != nil { + return nil, err + } + image, err := container.GetImage() if err != nil { return nil, err } - return image.Changes(container.rwPath()) + return image.Changes(container.runtime, container.RootfsPath(), container.rwPath(), container.ID) } func (container *Container) GetImage() (*Image, error) { diff --git a/image.go b/image.go index 9d9a64cceb..119e07c70e 100644 --- a/image.go +++ b/image.go @@ -486,12 +486,41 @@ func (image *Image) Unmount(runtime *Runtime, root string, id string) error { return nil } -func (image *Image) Changes(rw string) ([]Change, error) { - layers, err := image.layers() - if err != nil { - return nil, err +func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, error) { + switch runtime.GetMountMethod() { + case MountMethodAUFS: + layers, err := image.layers() + if err != nil { + return nil, err + } + return ChangesAUFS(layers, rw) + + case MountMethodDeviceMapper: + devices, err := runtime.GetDeviceSet() + if err != nil { + return nil, err + } + + if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { + return nil, err + } + + // We re-use rw for the temporary mount of the base image as its + // not used by device-mapper otherwise + err = devices.MountDevice(image.ID, rw) + if err != nil { + return nil, err + } + + changes, err := ChangesDirs(root, rw) + _ = syscall.Unmount(rw, 0) + if err != nil { + return nil, err + } + return changes, nil } - return Changes(layers, rw) + + return nil, fmt.Errorf("No supported Changes implementation") } func (image *Image) ShortID() string { From 8f23945f7f666e3151890de90ebb39db3c7f5ada Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 20:11:18 +0200 Subject: [PATCH 016/126] Archive: Fix up tar commandline arguments in TarFilter() There is no need to duplicate the compression flags for every element in the filter. --- archive.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/archive.go b/archive.go index bb019fb033..80b305a418 100644 --- a/archive.go +++ b/archive.go @@ -90,8 +90,9 @@ func TarFilter(path string, compression Compression, filter []string) (io.Reader if filter == nil { filter = []string{"."} } + args = append(args, "-c"+compression.Flag()) for _, f := range filter { - args = append(args, "-c"+compression.Flag(), f) + args = append(args, f) } return CmdStream(exec.Command(args[0], args[1:]...)) } From 223280f31967edb7b3eb325cce8a3b82939c0f2b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 22:10:29 +0200 Subject: [PATCH 017/126] Make TarFilter more useful There are a few changes: * Callers can specify if they want recursive behaviour or not * All file listings to tar are sent on stdin, to handle long lists better * We can pass in a list of filenames which will be created as empty files in the tarball This is exactly what we want for the creation of layer tarballs given a container fs, a set of files to add and a set of whiteout files to create. --- archive.go | 82 ++++++++++++++++++++++++++++++++++++++++++++----- archive_test.go | 6 ++-- container.go | 2 +- 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/archive.go b/archive.go index 80b305a418..f3f7b8c59e 100644 --- a/archive.go +++ b/archive.go @@ -80,21 +80,73 @@ func (compression *Compression) Extension() string { // Tar creates an archive from the directory at `path`, and returns it as a // stream of bytes. func Tar(path string, compression Compression) (io.Reader, error) { - return TarFilter(path, compression, nil) + return TarFilter(path, compression, nil, true, nil) +} + +func escapeName(name string) string { + escaped := make([]byte,0) + for i, c := range []byte(name) { + if i == 0 && c == '/' { + continue + } + // all printable chars except "-" which is 0x2d + if (0x20 <= c && c <= 0x7E) && c != 0x2d { + escaped = append(escaped, c) + } else { + escaped = append(escaped, fmt.Sprintf("\\%03o", c)...) + } + } + return string(escaped) } // Tar creates an archive from the directory at `path`, only including files whose relative // paths are included in `filter`. If `filter` is nil, then all files are included. -func TarFilter(path string, compression Compression, filter []string) (io.Reader, error) { - args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path} +func TarFilter(path string, compression Compression, filter []string, recursive bool, createFiles []string) (io.Reader, error) { + args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-",} if filter == nil { filter = []string{"."} } args = append(args, "-c"+compression.Flag()) - for _, f := range filter { - args = append(args, f) + + if !recursive { + args = append(args, "--no-recursion") } - return CmdStream(exec.Command(args[0], args[1:]...)) + + files := "" + for _, f := range filter { + files = files + escapeName(f) + "\n" + } + + tmpDir := "" + + if createFiles != nil { + tmpDir, err := ioutil.TempDir("", "docker-tar") + if err != nil { + return nil, err + } + + files = files + "-C" + tmpDir + "\n" + for _, f := range createFiles { + path := filepath.Join(tmpDir, f) + err := os.MkdirAll(filepath.Dir(path), 0600) + if err != nil { + return nil, err + } + + if file, err := os.OpenFile(path, os.O_CREATE, 0600); err != nil { + return nil, err + } else { + file.Close() + } + files = files + escapeName(f) + "\n" + } + } + + return CmdStream(exec.Command(args[0], args[1:]...), &files, func () { + if tmpDir != "" { + _ = os.RemoveAll(tmpDir) + } + }) } // Untar reads a stream of bytes from `archive`, parses it as a tar archive, @@ -141,7 +193,7 @@ func Untar(archive io.Reader, path string) error { // TarUntar aborts and returns the error. func TarUntar(src string, filter []string, dst string) error { utils.Debugf("TarUntar(%s %s %s)", src, filter, dst) - archive, err := TarFilter(src, Uncompressed, filter) + archive, err := TarFilter(src, Uncompressed, filter, true, nil) if err != nil { return err } @@ -228,7 +280,18 @@ func CopyFileWithTar(src, dst string) error { // CmdStream executes a command, and returns its stdout as a stream. // If the command fails to run or doesn't complete successfully, an error // will be returned, including anything written on stderr. -func CmdStream(cmd *exec.Cmd) (io.Reader, error) { +func CmdStream(cmd *exec.Cmd, input *string, atEnd func()) (io.Reader, error) { + if input != nil { + stdin, err := cmd.StdinPipe() + if err != nil { + return nil, err + } + // Write stdin if any + go func() { + _, _ = stdin.Write([]byte(*input)) + stdin.Close() + }() + } stdout, err := cmd.StdoutPipe() if err != nil { return nil, err @@ -259,6 +322,9 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) { } else { pipeW.Close() } + if atEnd != nil { + atEnd() + } }() // Run the command and return the pipe if err := cmd.Start(); err != nil { diff --git a/archive_test.go b/archive_test.go index 9a0a8e1b9e..c86b4511c4 100644 --- a/archive_test.go +++ b/archive_test.go @@ -14,7 +14,7 @@ import ( func TestCmdStreamLargeStderr(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello") - out, err := CmdStream(cmd) + out, err := CmdStream(cmd, nil, nil) if err != nil { t.Fatalf("Failed to start command: %s", err) } @@ -35,7 +35,7 @@ func TestCmdStreamLargeStderr(t *testing.T) { func TestCmdStreamBad(t *testing.T) { badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1") - out, err := CmdStream(badCmd) + out, err := CmdStream(badCmd, nil, nil) if err != nil { t.Fatalf("Failed to start command: %s", err) } @@ -50,7 +50,7 @@ func TestCmdStreamBad(t *testing.T) { func TestCmdStreamGood(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0") - out, err := CmdStream(cmd) + out, err := CmdStream(cmd, nil, nil) if err != nil { t.Fatal(err) } diff --git a/container.go b/container.go index 33b410ee5a..bd1d5a41a5 100644 --- a/container.go +++ b/container.go @@ -1277,5 +1277,5 @@ func (container *Container) Copy(resource string) (Archive, error) { filter = []string{path.Base(basePath)} basePath = path.Dir(basePath) } - return TarFilter(basePath, Uncompressed, filter) + return TarFilter(basePath, Uncompressed, filter, true, nil) } From 94fa3c7bb5cadc31b64630b0fe8abfaeba0644aa Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 22:14:19 +0200 Subject: [PATCH 018/126] Implement container.ExportRW() on device-mapper --- container.go | 10 +++++++++- image.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/container.go b/container.go index bd1d5a41a5..fcbad17c86 100644 --- a/container.go +++ b/container.go @@ -1110,7 +1110,15 @@ func (container *Container) Resize(h, w int) error { } func (container *Container) ExportRw() (Archive, error) { - return Tar(container.rwPath(), Uncompressed) + if err := container.EnsureMounted(); err != nil { + return nil, err + } + + image, err := container.GetImage() + if err != nil { + return nil, err + } + return image.ExportChanges(container.runtime, container.RootfsPath(), container.rwPath(), container.ID) } func (container *Container) RwChecksum() (string, error) { diff --git a/image.go b/image.go index 119e07c70e..546c54a577 100644 --- a/image.go +++ b/image.go @@ -523,6 +523,37 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er return nil, fmt.Errorf("No supported Changes implementation") } +func (image *Image) ExportChanges(runtime *Runtime, root, rw, id string) (Archive, error) { + switch runtime.GetMountMethod() { + case MountMethodAUFS: + return Tar(rw, Uncompressed) + + case MountMethodDeviceMapper: + changes, err := image.Changes(runtime, root, rw, id) + if err != nil { + return nil, err + } + + files := make([]string, 0) + deletions := make([]string, 0) + for _, change := range changes { + if change.Kind == ChangeModify || change.Kind == ChangeAdd { + files = append(files, change.Path) + } + if change.Kind == ChangeDelete { + base := filepath.Base(change.Path) + dir := filepath.Dir(change.Path) + deletions = append(deletions, filepath.Join(dir, ".wh."+base)) + } + } + + return TarFilter(root, Uncompressed, files, false, deletions) + } + + return nil, fmt.Errorf("No supported Changes implementation") +} + + func (image *Image) ShortID() string { return utils.TruncateID(image.ID) } From 19ba0b851bb00248b62a40695a60fc534d0df2cb Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 12:03:21 +0200 Subject: [PATCH 019/126] Runtime: Delete corresponding devices when deleting container --- runtime.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/runtime.go b/runtime.go index 1a89340075..d207f5186d 100644 --- a/runtime.go +++ b/runtime.go @@ -282,6 +282,11 @@ func (runtime *Runtime) Destroy(container *Container) error { if err := os.RemoveAll(container.root); err != nil { return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err) } + if runtime.GetMountMethod() == MountMethodDeviceMapper { + if err := runtime.deviceSet.RemoveDevice(container.ID); err != nil { + return fmt.Errorf("Unable to remove device for %v: %v", container.ID, err) + } + } return nil } From 3f3f5f0bbaafc67d3f77b920194f8d7bfb7bf6ee Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 12:04:00 +0200 Subject: [PATCH 020/126] Delete corresponding Devices when deleting Images If an image is deleted and there is a corresponding device for that image we also delete the image. --- runtime.go | 13 +++++++++++++ runtime_test.go | 2 +- server.go | 4 ++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/runtime.go b/runtime.go index d207f5186d..ea62353a68 100644 --- a/runtime.go +++ b/runtime.go @@ -290,6 +290,19 @@ func (runtime *Runtime) Destroy(container *Container) error { return nil } +func (runtime *Runtime) DeleteImage(id string) error { + err := runtime.graph.Delete(id) + if err != nil { + return err + } + if runtime.GetMountMethod() == MountMethodDeviceMapper && runtime.deviceSet.HasDevice(id) { + if err := runtime.deviceSet.RemoveDevice(id); err != nil { + return fmt.Errorf("Unable to remove device for %v: %v", id, err) + } + } + return nil +} + func (runtime *Runtime) restore() error { wheel := "-\\|/" if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { diff --git a/runtime_test.go b/runtime_test.go index 503f519d12..f9a209008c 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -57,7 +57,7 @@ func cleanup(runtime *Runtime) error { } for _, image := range images { if image.ID != unitTestImageID { - runtime.graph.Delete(image.ID) + runtime.DeleteImage(image.ID) } } return nil diff --git a/server.go b/server.go index 1b4c0790ae..7f82d7adab 100644 --- a/server.go +++ b/server.go @@ -1025,7 +1025,7 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error { if err := srv.runtime.repositories.DeleteAll(id); err != nil { return err } - err := srv.runtime.graph.Delete(id) + err := srv.runtime.DeleteImage(id) if err != nil { return err } @@ -1099,7 +1099,7 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) { return nil, fmt.Errorf("No such image: %s", name) } if !autoPrune { - if err := srv.runtime.graph.Delete(img.ID); err != nil { + if err := srv.runtime.DeleteImage(img.ID); err != nil { return nil, fmt.Errorf("Error deleting image %s: %s", name, err) } return nil, nil From 0e686fa2f4d38eb6253e92ad701dd4c9caebfdce Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 14:37:04 +0200 Subject: [PATCH 021/126] Add DeviceSetWrapper This wraps an existing DeviceSet and just adds a prefix to all ids in it. This will be useful for reusing a single DeviceSet for all the tests (but with separate ids) --- deviceset.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/deviceset.go b/deviceset.go index 01bdba411f..2caaf153b2 100644 --- a/deviceset.go +++ b/deviceset.go @@ -9,3 +9,52 @@ type DeviceSet interface { HasDevice(hash string) bool HasInitializedDevice(hash string) bool } + +type DeviceSetWrapper struct { + wrapped DeviceSet + prefix string +} + +func (wrapper *DeviceSetWrapper) wrap(hash string) string { + if hash != "" { + hash = wrapper.prefix + "-" + hash + } + return hash +} + + +func (wrapper *DeviceSetWrapper) AddDevice(hash, baseHash string) error { + return wrapper.wrapped.AddDevice(wrapper.wrap(hash), wrapper.wrap(baseHash)) +} + +func (wrapper *DeviceSetWrapper) SetInitialized(hash string) error { + return wrapper.wrapped.SetInitialized(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) DeactivateDevice(hash string) error { + return wrapper.wrapped.DeactivateDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) RemoveDevice(hash string) error { + return wrapper.wrapped.RemoveDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) MountDevice(hash, path string) error { + return wrapper.wrapped.MountDevice(wrapper.wrap(hash), path) +} + +func (wrapper *DeviceSetWrapper) HasDevice(hash string) bool { + return wrapper.wrapped.HasDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) HasInitializedDevice(hash string) bool { + return wrapper.wrapped.HasInitializedDevice(wrapper.wrap(hash)) +} + +func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { + wrapper := &DeviceSetWrapper{ + wrapped: wrapped, + prefix: prefix, + } + return wrapper +} From d47c18c5fbe50a2ad6ec011704f86a3c27360ff9 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 14:38:47 +0200 Subject: [PATCH 022/126] Reuse a single DeviceSetDM for all the tests We wrap the "real" DeviceSet for each test so that we get only a single device-mapper pool and loopback mounts, but still separate out the IDs in the tests. This makes the test run much faster. --- runtime_test.go | 2 +- utils_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime_test.go b/runtime_test.go index f9a209008c..5fa6c46bfe 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -457,7 +457,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, devmapper.NewDeviceSetDM(runtime1.root), false) + runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false) if err != nil { t.Fatal(err) } diff --git a/utils_test.go b/utils_test.go index bc4dd65a3c..f5bdac3271 100644 --- a/utils_test.go +++ b/utils_test.go @@ -2,7 +2,7 @@ package docker import ( "github.com/dotcloud/docker/utils" - "github.com/dotcloud/docker/devmapper" + "path/filepath" "io" "io/ioutil" "os" @@ -43,7 +43,7 @@ func newTestRuntime() (*Runtime, error) { return nil, err } - runtime, err := NewRuntimeFromDirectory(root, devmapper.NewDeviceSetDM(root), false) + runtime, err := NewRuntimeFromDirectory(root, NewDeviceSetWrapper (globalRuntime.deviceSet, filepath.Base(root)), false) if err != nil { return nil, err } From bc7fa7b95773d638754eb72e7921ac328acb2ad6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 16:15:44 +0200 Subject: [PATCH 023/126] Limit the amount of prints during normal runs This removes some Debugf() calls and chages some direct prints to Debugf(). This means we don't get a bunch of spew when running the tests. --- devmapper/deviceset_devmapper.go | 3 ++- image.go | 10 +++------- runtime.go | 6 +++--- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go index 4544215b63..d163609a7f 100644 --- a/devmapper/deviceset_devmapper.go +++ b/devmapper/deviceset_devmapper.go @@ -1,6 +1,7 @@ package devmapper import ( + "github.com/dotcloud/docker/utils" "encoding/json" "fmt" "io" @@ -184,7 +185,7 @@ func (devices *DeviceSetDM) ensureImage(name string, size int64) (string, error) } func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) error { - log.Printf("Activating device-mapper pool %s", devices.getPoolName()) + utils.Debugf("Activating device-mapper pool %s", devices.getPoolName()) task, err := devices.createTask(DeviceCreate, devices.getPoolName()) if task == nil { return err diff --git a/image.go b/image.go index 546c54a577..081d28249a 100644 --- a/image.go +++ b/image.go @@ -339,7 +339,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { mounted, err := Mounted(mountDir) if err == nil && mounted { - log.Printf("Image %s is unexpectedly mounted, unmounting...", image.ID) + utils.Debugf("Image %s is unexpectedly mounted, unmounting...", image.ID) err = syscall.Unmount(mountDir, 0) if err != nil { return err @@ -347,21 +347,19 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { } if devices.HasDevice(image.ID) { - log.Printf("Found non-initialized demove-mapper device for image %s, removing", image.ID) + utils.Debugf("Found non-initialized demove-mapper device for image %s, removing", image.ID) err = devices.RemoveDevice(image.ID) if err != nil { return err } } - log.Printf("Creating device-mapper device for image id %s", image.ID) - + utils.Debugf("Creating device-mapper device for image id %s", image.ID) err = devices.AddDevice(image.ID, image.Parent) if err != nil { return err } - utils.Debugf("Mounting device %s at %s for image setup", image.ID, mountDir) err = devices.MountDevice(image.ID, mountDir) if err != nil { _ = devices.RemoveDevice(image.ID) @@ -375,14 +373,12 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } - utils.Debugf("Applying layer %s at %s", image.ID, mountDir) err = image.applyLayer(layerPath(root), mountDir) if err != nil { _ = devices.RemoveDevice(image.ID) return err } - utils.Debugf("Unmounting %s", mountDir) err = syscall.Unmount(mountDir, 0) if err != nil { _ = devices.RemoveDevice(image.ID) diff --git a/runtime.go b/runtime.go index ea62353a68..1e07141780 100644 --- a/runtime.go +++ b/runtime.go @@ -107,15 +107,15 @@ func (runtime *Runtime) GetMountMethod() MountMethod { if runtime.mountMethod == MountMethodNone { // Try to automatically pick a method if hasFilesystemSupport("aufs") { - log.Printf("Using AUFS backend.") + utils.Debugf("Using AUFS backend.") runtime.mountMethod = MountMethodAUFS } else { _ = exec.Command("modprobe", "aufs").Run() if hasFilesystemSupport("aufs") { - log.Printf("Using AUFS backend.") + utils.Debugf("Using AUFS backend.") runtime.mountMethod = MountMethodAUFS } else { - log.Printf("Using device-mapper backend.") + utils.Debugf("Using device-mapper backend.") runtime.mountMethod = MountMethodDeviceMapper } } From 6938a36c6985336205f1db247baec5e56fdac466 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 10 Sep 2013 21:04:25 +0200 Subject: [PATCH 024/126] Allow specifying the docker client path in _DOCKER_INIT_PATH I currently need this to get the tests running, otherwise it will mount the docker.test binary inside the containers, which doesn't work due to the libdevmapper.so dependency. --- runtime.go | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/runtime.go b/runtime.go index 1e07141780..30250e713b 100644 --- a/runtime.go +++ b/runtime.go @@ -52,16 +52,21 @@ type Runtime struct { var sysInitPath string func init() { - selfPath := utils.SelfPath() - - // If we have a separate docker-init, use that, otherwise use the - // main docker binary - dir := filepath.Dir(selfPath) - dockerInitPath := filepath.Join(dir, "docker-init") - if _, err := os.Stat(dockerInitPath); err != nil { - sysInitPath = selfPath + env := os.Getenv("_DOCKER_INIT_PATH") + if env != "" { + sysInitPath = env } else { - sysInitPath = dockerInitPath + selfPath := utils.SelfPath() + + // If we have a separate docker-init, use that, otherwise use the + // main docker binary + dir := filepath.Dir(selfPath) + dockerInitPath := filepath.Join(dir, "docker-init") + if _, err := os.Stat(dockerInitPath); err != nil { + sysInitPath = selfPath + } else { + sysInitPath = dockerInitPath + } } } From a7fd1fce5d6fb29a8c627022da7cbbf0f4b740c7 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 11:46:16 +0200 Subject: [PATCH 025/126] tests: Store the loopback images for test outside unit-tests This directory is copied to each test prefix which is really slow with the large loopback mounts. --- runtime_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runtime_test.go b/runtime_test.go index 5fa6c46bfe..6e5ca00518 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -23,6 +23,7 @@ const ( unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 unitTestNetworkBridge = "testdockbr0" unitTestStoreBase = "/var/lib/docker/unit-tests" + unitTestStoreDevicesBase = "/var/lib/docker/unit-tests-devices" testDaemonAddr = "127.0.0.1:4270" testDaemonProto = "tcp" ) @@ -88,7 +89,7 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreBase), false); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { panic(err) } else { globalRuntime = runtime From 261b0b01dffde42595d987b609d38c3b6d8368f7 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 12:06:55 +0200 Subject: [PATCH 026/126] Always start tests from a clean set of loopback images This way we don't get any issues with leftovers --- runtime_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/runtime_test.go b/runtime_test.go index 6e5ca00518..5bf9af5fdb 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -88,6 +88,12 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge + // Always start from a clean set of loopback mounts + err := os.RemoveAll(unitTestStoreDevicesBase) + if err != nil { + panic(err) + } + // Make it our Store root if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { panic(err) From 251a7ed437c17ecb66d33782f0b42633033198dd Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 12:39:42 +0200 Subject: [PATCH 027/126] DeviceSet: Add UnmountDevice() Right now this does nothing but add a new layer, but it means that all DeviceMounts are paired with DeviceUnmounts so that we can track (and cleanup) active mounts. --- deviceset.go | 5 +++++ devmapper/deviceset_devmapper.go | 12 +++++++++++- image.go | 15 ++++++++------- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/deviceset.go b/deviceset.go index 2caaf153b2..95a3d48f27 100644 --- a/deviceset.go +++ b/deviceset.go @@ -6,6 +6,7 @@ type DeviceSet interface { DeactivateDevice(hash string) error RemoveDevice(hash string) error MountDevice(hash, path string) error + UnmountDevice(hash, path string) error HasDevice(hash string) bool HasInitializedDevice(hash string) bool } @@ -43,6 +44,10 @@ func (wrapper *DeviceSetWrapper) MountDevice(hash, path string) error { return wrapper.wrapped.MountDevice(wrapper.wrap(hash), path) } +func (wrapper *DeviceSetWrapper) UnmountDevice(hash, path string) error { + return wrapper.wrapped.UnmountDevice(wrapper.wrap(hash), path) +} + func (wrapper *DeviceSetWrapper) HasDevice(hash string) bool { return wrapper.wrapped.HasDevice(wrapper.wrap(hash)) } diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go index d163609a7f..e31515042c 100644 --- a/devmapper/deviceset_devmapper.go +++ b/devmapper/deviceset_devmapper.go @@ -637,7 +637,7 @@ func (devices *DeviceSetDM) setupBaseImage() error { return err } - err = syscall.Unmount(tmpDir, 0) + err = devices.UnmountDevice("", tmpDir) if err != nil { return err } @@ -840,6 +840,16 @@ func (devices *DeviceSetDM) MountDevice(hash, path string) error { return nil } +func (devices *DeviceSetDM) UnmountDevice(hash, path string) error { + err := syscall.Unmount(path, 0) + if err != nil { + return err + } + + return nil +} + + func (devices *DeviceSetDM) HasDevice(hash string) bool { if err := devices.ensureInit(); err != nil { return false diff --git a/image.go b/image.go index 081d28249a..da03fb8ff1 100644 --- a/image.go +++ b/image.go @@ -379,7 +379,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } - err = syscall.Unmount(mountDir, 0) + err = devices.UnmountDevice(image.ID, mountDir) if err != nil { _ = devices.RemoveDevice(image.ID) return err @@ -467,16 +467,17 @@ func (image *Image) Unmount(runtime *Runtime, root string, id string) error { return Unmount(root) case MountMethodDeviceMapper: - err := syscall.Unmount(root, 0) - if err != nil { - return err - } - // Try to deactivate the device as generally there is no use for it anymore devices, err := runtime.GetDeviceSet() if err != nil { return err; } + + err = devices.UnmountDevice(id, root) + if err != nil { + return err + } + return devices.DeactivateDevice(id) } return nil @@ -509,7 +510,7 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er } changes, err := ChangesDirs(root, rw) - _ = syscall.Unmount(rw, 0) + _ = devices.UnmountDevice(image.ID, rw) if err != nil { return nil, err } From c6e8813c979bbea8832f47dc6468e805a1a18c3e Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 13:47:29 +0200 Subject: [PATCH 028/126] deviceset: Cleanup device sets on test end We unmount all mounts and deactivate all device mapper devices to make sure we're left with no leftovers after the test. --- deviceset.go | 5 +++ devmapper/deviceset_devmapper.go | 54 +++++++++++++++++++++++++++++--- runtime_test.go | 7 +++++ z_final_test.go | 2 +- 4 files changed, 63 insertions(+), 5 deletions(-) diff --git a/deviceset.go b/deviceset.go index 95a3d48f27..5dd608361f 100644 --- a/deviceset.go +++ b/deviceset.go @@ -9,6 +9,7 @@ type DeviceSet interface { UnmountDevice(hash, path string) error HasDevice(hash string) bool HasInitializedDevice(hash string) bool + Shutdown() error } type DeviceSetWrapper struct { @@ -36,6 +37,10 @@ func (wrapper *DeviceSetWrapper) DeactivateDevice(hash string) error { return wrapper.wrapped.DeactivateDevice(wrapper.wrap(hash)) } +func (wrapper *DeviceSetWrapper) Shutdown() error { + return nil +} + func (wrapper *DeviceSetWrapper) RemoveDevice(hash string) error { return wrapper.wrapped.RemoveDevice(wrapper.wrap(hash)) } diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go index e31515042c..4fcfac4464 100644 --- a/devmapper/deviceset_devmapper.go +++ b/devmapper/deviceset_devmapper.go @@ -40,6 +40,7 @@ type DeviceSetDM struct { TransactionId uint64 NewTransactionId uint64 nextFreeDevice int + activeMounts map[string]int } func getDevName(name string) string { @@ -348,8 +349,8 @@ func (devices *DeviceSetDM) deleteDevice(deviceId int) error { return nil } -func (devices *DeviceSetDM) removeDevice(info *DevInfo) error { - task, err := devices.createTask(DeviceRemove, info.Name()) +func (devices *DeviceSetDM) removeDevice(name string) error { + task, err := devices.createTask(DeviceRemove, name) if task == nil { return err } @@ -763,7 +764,7 @@ func (devices *DeviceSetDM) RemoveDevice(hash string) error { devinfo, _ := devices.getInfo(info.Name()) if devinfo != nil && devinfo.Exists != 0 { - err := devices.removeDevice(info) + err := devices.removeDevice(info.Name()) if err != nil { return err } @@ -809,7 +810,7 @@ func (devices *DeviceSetDM) DeactivateDevice(hash string) error { return err } if devinfo.Exists != 0 { - err := devices.removeDevice(info) + err := devices.removeDevice(info.Name()) if err != nil { return err } @@ -818,6 +819,39 @@ func (devices *DeviceSetDM) DeactivateDevice(hash string) error { return nil } +func (devices *DeviceSetDM) Shutdown() error { + if !devices.initialized { + return nil + } + + for path, count := range devices.activeMounts { + for i := count; i > 0; i-- { + err := syscall.Unmount(path, 0) + if err != nil { + fmt.Printf("Shutdown unmounting %s, error: %s\n", path, err) + } + } + delete(devices.activeMounts, path) + } + + for _, d := range devices.Devices { + if err := devices.DeactivateDevice(d.Hash); err != nil { + fmt.Printf("Shutdown deactivate %s , error: %s\n", d.Hash, err) + } + } + + + pool := devices.getPoolDevName() + devinfo, err := devices.getInfo(pool) + if err == nil && devinfo.Exists != 0 { + if err := devices.removeDevice(pool); err != nil { + fmt.Printf("Shutdown deactivate %s , error: %s\n", pool, err) + } + } + + return nil +} + func (devices *DeviceSetDM) MountDevice(hash, path string) error { if err := devices.ensureInit(); err != nil { return err @@ -837,6 +871,10 @@ func (devices *DeviceSetDM) MountDevice(hash, path string) error { if err != nil { return err } + + count := devices.activeMounts[path] + devices.activeMounts[path] = count + 1 + return nil } @@ -846,6 +884,13 @@ func (devices *DeviceSetDM) UnmountDevice(hash, path string) error { return err } + count := devices.activeMounts[path] + if count > 1 { + devices.activeMounts[path] = count - 1 + } else { + delete(devices.activeMounts, path) + } + return nil } @@ -913,6 +958,7 @@ func NewDeviceSetDM(root string) *DeviceSetDM { devicePrefix: base, } devices.Devices = make(map[string]*DevInfo) + devices.activeMounts = make(map[string]int) return devices } diff --git a/runtime_test.go b/runtime_test.go index 5bf9af5fdb..ccdf4d9563 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -64,6 +64,13 @@ func cleanup(runtime *Runtime) error { return nil } +func cleanupLast(runtime *Runtime) error { + cleanup(runtime) + runtime.deviceSet.Shutdown() + return nil +} + + func layerArchive(tarfile string) (io.Reader, error) { // FIXME: need to close f somewhere f, err := os.Open(tarfile) diff --git a/z_final_test.go b/z_final_test.go index 08a180baaf..c52f87cddb 100644 --- a/z_final_test.go +++ b/z_final_test.go @@ -11,7 +11,7 @@ func displayFdGoroutines(t *testing.T) { } func TestFinal(t *testing.T) { - cleanup(globalRuntime) + cleanupLast(globalRuntime) t.Logf("Start Fds: %d, Start Goroutines: %d", startFds, startGoroutines) displayFdGoroutines(t) } From 3343b3f8f8520c2a1a841675609b4b2c54f3533d Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 13:48:58 +0200 Subject: [PATCH 029/126] graph test: Unmount image via image.Unmount() This helps us track the unmount --- graph_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph_test.go b/graph_test.go index d89e6efe9d..ecb5ffb34e 100644 --- a/graph_test.go +++ b/graph_test.go @@ -152,7 +152,7 @@ func TestMount(t *testing.T) { } // FIXME: test for mount contents defer func() { - if err := Unmount(rootfs); err != nil { + if err := image.Unmount(runtime, rootfs, "testing"); err != nil { t.Error(err) } }() From e1c418cac3cf06d453c7ac10dd54b2b0f617f1f6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 14:18:20 +0200 Subject: [PATCH 030/126] Runtime: Only remove device on destroy if it exists --- runtime.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime.go b/runtime.go index 30250e713b..54071e96a9 100644 --- a/runtime.go +++ b/runtime.go @@ -287,7 +287,7 @@ func (runtime *Runtime) Destroy(container *Container) error { if err := os.RemoveAll(container.root); err != nil { return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err) } - if runtime.GetMountMethod() == MountMethodDeviceMapper { + if runtime.GetMountMethod() == MountMethodDeviceMapper && runtime.deviceSet.HasDevice(container.ID) { if err := runtime.deviceSet.RemoveDevice(container.ID); err != nil { return fmt.Errorf("Unable to remove device for %v: %v", container.ID, err) } From 2566e2604c2e079f9597749a1da11f22bb39eb51 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 14:44:19 +0200 Subject: [PATCH 031/126] api_test: Fix PostContainersCreate We can't look for the created file in the rwpath, because that doesn't exist in the device-mapper world, instead look in the RootfsPath. --- api_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/api_test.go b/api_test.go index d24cf7cfda..edff6788e1 100644 --- a/api_test.go +++ b/api_test.go @@ -647,13 +647,21 @@ func TestPostContainersCreate(t *testing.T) { t.Fatal(err) } - if _, err := os.Stat(path.Join(container.rwPath(), "test")); err != nil { + if err := container.EnsureMounted(); err != nil { + t.Fatalf("Unable to mount container: %s", err) + } + + if _, err := os.Stat(path.Join(container.RootfsPath(), "test")); err != nil { if os.IsNotExist(err) { utils.Debugf("Err: %s", err) t.Fatalf("The test file has not been created") } t.Fatal(err) } + + if err := container.Unmount(); err != nil { + t.Fatalf("Unable to unmount container: %s", err) + } } func TestPostContainersKill(t *testing.T) { From 20bac716b525e1cbd2a778122a4ce41c0c2768c2 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 15:56:59 +0200 Subject: [PATCH 032/126] Container: Inject into the mount, not the rwPath For device-mapper setups we can't just push the file into the rwPath. --- container.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/container.go b/container.go index fcbad17c86..d5dbddb773 100644 --- a/container.go +++ b/container.go @@ -292,12 +292,17 @@ func (settings *NetworkSettings) PortMappingAPI() []APIPort { // Inject the io.Reader at the given path. Note: do not close the reader func (container *Container) Inject(file io.Reader, pth string) error { - // Make sure the directory exists - if err := os.MkdirAll(path.Join(container.rwPath(), path.Dir(pth)), 0755); err != nil { + if err := container.EnsureMounted(); err != nil { return err } + + // Make sure the directory exists + if err := os.MkdirAll(path.Join(container.RootfsPath(), path.Dir(pth)), 0755); err != nil { + return err + } + // FIXME: Handle permissions/already existing dest - dest, err := os.Create(path.Join(container.rwPath(), pth)) + dest, err := os.Create(path.Join(container.RootfsPath(), pth)) if err != nil { return err } From d80be57c1538905a46f75c95d592acec49a498d6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 12 Sep 2013 15:17:39 +0200 Subject: [PATCH 033/126] Utils: Add ShellQuoteArguments --- utils/utils.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/utils/utils.go b/utils/utils.go index d417690c0c..94aa0dc902 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1021,3 +1021,36 @@ type StatusError struct { func (e *StatusError) Error() string { return fmt.Sprintf("Status: %d", e.Status) } + +func quote(word string, buf *bytes.Buffer) { + // Bail out early for "simple" strings + if word != "" && !strings.ContainsAny(word, "\\'\"`${[|&;<>()~*?! \t\n") { + buf.WriteString(word) + return + } + + buf.WriteString("''") + + for i := 0; i < len(word); i++ { + b := word[i] + if b == '\'' { + // Replace literal ' with a close ', a \', and a open ' + buf.WriteString("'\\''") + } else { + buf.WriteByte(b) + } + } + + buf.WriteString("''") +} + +func ShellQuoteArguments(args []string) string { + var buf bytes.Buffer + for i, arg := range args { + if i != 0 { + buf.WriteByte(' ') + } + quote(arg, &buf) + } + return buf.String() +} From e40f5c7cb90fbc719241ace45b05c2c61aced658 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 12 Sep 2013 15:18:11 +0200 Subject: [PATCH 034/126] lxc: Work around lxc-start need for private mounts lxc-start requires / to be mounted private, otherwise the changes it does inside the container (both mounts and unmounts) will propagate out to the host. We work around this by starting up lxc-start in its own namespace where we set / to rprivate. Unfortunately go can't really execute any code between clone and exec, so we can't do this in a nice way. Instead we have a horrible hack that use the unshare command, the shell and the mount command... --- container.go | 17 ++++++++++++++++- utils.go | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/container.go b/container.go index d5dbddb773..549449080c 100644 --- a/container.go +++ b/container.go @@ -747,6 +747,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { } params := []string{ + "lxc-start", "-n", container.ID, "-f", container.lxcConfigPath(), "--", @@ -795,7 +796,21 @@ func (container *Container) Start(hostConfig *HostConfig) error { params = append(params, "--", container.Path) params = append(params, container.Args...) - container.cmd = exec.Command("lxc-start", params...) + if RootIsShared() { + // lxc-start really needs / to be private, or all kinds of stuff break + // What we really want is to clone into a new namespace and then + // mount / MS_REC|MS_PRIVATE, but since we can't really clone or fork + // without exec in go we have to do this horrible shell hack... + shellString := + "mount --make-rprivate /; exec " + + utils.ShellQuoteArguments(params) + + params = []string{ + "unshare", "-m", "--", "/bin/sh", "-c", shellString, + } + } + + container.cmd = exec.Command(params[0], params[1:]...) // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { diff --git a/utils.go b/utils.go index aed8ffdd76..babca65bf1 100644 --- a/utils.go +++ b/utils.go @@ -2,6 +2,7 @@ package docker import ( "fmt" + "io/ioutil" "strings" ) @@ -167,3 +168,17 @@ func parseLxcOpt(opt string) (string, string, error) { } return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil } + +func RootIsShared() bool { + if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { + for _, line := range strings.Split(string(data), "\n") { + cols := strings.Split(line, " ") + if cols[3] == "/" && cols[4] == "/" { + return strings.HasPrefix(cols[6], "shared") + } + } + } + + // No idea, probably safe to assume so + return true +} From c199ed228baf0e5d33b7739cc2442a32dece7020 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 12 Sep 2013 20:30:55 +0200 Subject: [PATCH 035/126] devmapper: Move init layer to top rather than bottom The init layer needs to be topmost to make sure certain files are always there (for instance, the ubuntu:12.10 image wrongly has /dev/shm being a symlink to /run/shm, and we need to override that). However, previously the devmapper code implemented the init layer by putting it in the base devmapper device, which meant layers above it could override these files (so that ubuntu:12.10 broke). So, instead we put the base layer in *each* images devmapper device. This is "safe" because we still have the pristine layer data in the layer directory. Also, it means we diff the container against the image with the init layer applied, so it won't show up in diffs/commits. --- devmapper/deviceset_devmapper.go | 62 -------------------------------- image.go | 17 +++++++++ 2 files changed, 17 insertions(+), 62 deletions(-) diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go index 4fcfac4464..670d7621c4 100644 --- a/devmapper/deviceset_devmapper.go +++ b/devmapper/deviceset_devmapper.go @@ -542,45 +542,6 @@ func (devices *DeviceSetDM) loadMetaData() error { return nil } -func (devices *DeviceSetDM) createBaseLayer(dir string) error { - for pth, typ := range map[string]string{ - "/dev/pts": "dir", - "/dev/shm": "dir", - "/proc": "dir", - "/sys": "dir", - "/.dockerinit": "file", - "/etc/resolv.conf": "file", - "/etc/hosts": "file", - "/etc/hostname": "file", - // "var/run": "dir", - // "var/lock": "dir", - } { - if _, err := os.Stat(path.Join(dir, pth)); err != nil { - if os.IsNotExist(err) { - switch typ { - case "dir": - if err := os.MkdirAll(path.Join(dir, pth), 0755); err != nil { - return err - } - case "file": - if err := os.MkdirAll(path.Join(dir, path.Dir(pth)), 0755); err != nil { - return err - } - - if f, err := os.OpenFile(path.Join(dir, pth), os.O_CREATE, 0755); err != nil { - return err - } else { - f.Close() - } - } - } else { - return err - } - } - } - return nil -} - func (devices *DeviceSetDM) setupBaseImage() error { oldInfo := devices.Devices[""] if oldInfo != nil && oldInfo.Initialized { @@ -622,29 +583,6 @@ func (devices *DeviceSetDM) setupBaseImage() error { return err } - tmpDir := path.Join(devices.loopbackDir(), "basefs") - if err = os.MkdirAll(tmpDir, 0700); err != nil && !os.IsExist(err) { - return err - } - - err = devices.MountDevice("", tmpDir) - if err != nil { - return err - } - - err = devices.createBaseLayer(tmpDir) - if err != nil { - _ = syscall.Unmount(tmpDir, 0) - return err - } - - err = devices.UnmountDevice("", tmpDir) - if err != nil { - return err - } - - _ = os.Remove(tmpDir) - info.Initialized = true err = devices.saveMetadata() diff --git a/image.go b/image.go index da03fb8ff1..1a279654d5 100644 --- a/image.go +++ b/image.go @@ -379,6 +379,23 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } + // The docker init layer is conceptually above all other layers, so we apply + // it for every image. This is safe because the layer directory is the + // definition of the image, and the device-mapper device is just a cache + // of it instantiated. Diffs/commit compare the container device with the + // image device, which will then *not* pick up the init layer changes as + // part of the container changes + dockerinitLayer, err := image.getDockerInitLayer() + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + err = image.applyLayer(dockerinitLayer, mountDir) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + err = devices.UnmountDevice(image.ID, mountDir) if err != nil { _ = devices.RemoveDevice(image.ID) From d478a4bb5401d7d657a2a100f98ee892a96fef2a Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 11:08:59 +0200 Subject: [PATCH 036/126] RootIsShared() - Fix array out of bounds error This happened for me on the last (empty) line, but better safe than sorry so we make the check general. --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index babca65bf1..57737f7b25 100644 --- a/utils.go +++ b/utils.go @@ -173,7 +173,7 @@ func RootIsShared() bool { if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { for _, line := range strings.Split(string(data), "\n") { cols := strings.Split(line, " ") - if cols[3] == "/" && cols[4] == "/" { + if len(cols) >= 6 && cols[3] == "/" && cols[4] == "/" { return strings.HasPrefix(cols[6], "shared") } } From 02b5f1369ce09d597336e77df98e56d467b8d1ff Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 12:56:58 +0200 Subject: [PATCH 037/126] Change how ChangesDirs() works Rather than scan the files in the old directory twice to detect the deletions we now scan both directories twice and then do all the diffing on the in-memory structure. This is more efficient, but it also lets us diff more complex things later that are not exact on-disk trees. --- changes.go | 201 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 143 insertions(+), 58 deletions(-) diff --git a/changes.go b/changes.go index 00c9cc7c77..d1b0a25b0d 100644 --- a/changes.go +++ b/changes.go @@ -106,50 +106,85 @@ func ChangesAUFS(layers []string, rw string) ([]Change, error) { return changes, nil } -func ChangesDirs(newDir, oldDir string) ([]Change, error) { - var changes []Change - err := filepath.Walk(newDir, func(newPath string, f os.FileInfo, err error) error { - if err != nil { - return err - } +type FileInfo struct { + parent *FileInfo + name string + stat syscall.Stat_t + children map[string]*FileInfo +} - var newStat syscall.Stat_t - err = syscall.Lstat(newPath, &newStat) - if err != nil { - return err - } +func (root *FileInfo) LookUp(path string) *FileInfo { + parent := root + if path == "/" { + return root + } - // Rebase path - relPath, err := filepath.Rel(newDir, newPath) - if err != nil { - return err - } - relPath = filepath.Join("/", relPath) - - // Skip root - if relPath == "/" || relPath == "/.docker-id" { - return nil - } - - change := Change{ - Path: relPath, - } - - oldPath := filepath.Join(oldDir, relPath) - - var oldStat = &syscall.Stat_t{} - err = syscall.Lstat(oldPath, oldStat) - if err != nil { - if !os.IsNotExist(err) { - return err + pathElements := strings.Split(path, "/") + for _, elem := range pathElements { + if elem != "" { + child := parent.children[elem] + if child == nil { + return nil } - oldStat = nil + parent = child } + } + return parent +} - if oldStat == nil { - change.Kind = ChangeAdd - changes = append(changes, change) - } else { +func (info *FileInfo)path() string { + if info.parent == nil { + return "/" + } + return filepath.Join(info.parent.path(), info.name) +} + +func (info *FileInfo)unlink() { + if info.parent != nil { + delete(info.parent.children, info.name) + } +} + +func (info *FileInfo)Remove(path string) bool { + child := info.LookUp(path) + if child != nil { + child.unlink() + return true + } + return false +} + +func (info *FileInfo)isDir() bool { + return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR +} + + +func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { + if oldInfo == nil { + // add + change := Change{ + Path: info.path(), + Kind: ChangeAdd, + } + *changes = append(*changes, change) + } + + // We make a copy so we can modify it to detect additions + // also, we only recurse on the old dir if the new info is a directory + // otherwise any previous delete/change is considered recursive + oldChildren := make(map[string]*FileInfo) + if oldInfo != nil && info.isDir() { + for k, v := range oldInfo.children { + oldChildren[k] = v + } + } + + for name, newChild := range info.children { + oldChild, _ := oldChildren[name] + if oldChild != nil { + // change? + oldStat := &oldChild.stat + newStat := &newChild.stat if oldStat.Ino != newStat.Ino || oldStat.Mode != newStat.Mode || oldStat.Uid != newStat.Uid || @@ -159,50 +194,100 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) { oldStat.Blocks != newStat.Blocks || oldStat.Mtim != newStat.Mtim || oldStat.Ctim != newStat.Ctim { - change.Kind = ChangeModify - changes = append(changes, change) + change := Change{ + Path: newChild.path(), + Kind: ChangeModify, + } + *changes = append(*changes, change) } + + // Remove from copy so we can detect deletions + delete(oldChildren, name) } - return nil - }) - if err != nil { - return nil, err + newChild.addChanges(oldChild, changes) } - err = filepath.Walk(oldDir, func(oldPath string, f os.FileInfo, err error) error { + for _, oldChild := range oldChildren { + // delete + change := Change{ + Path: oldChild.path(), + Kind: ChangeDelete, + } + *changes = append(*changes, change) + } + + +} + +func (info *FileInfo)Changes(oldInfo *FileInfo) []Change { + var changes []Change + + info.addChanges(oldInfo, &changes) + + return changes +} + + +func collectFileInfo(sourceDir string) (*FileInfo, error) { + root := &FileInfo { + name: "/", + children: make(map[string]*FileInfo), + } + + err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error { if err != nil { return err } // Rebase path - relPath, err := filepath.Rel(oldDir, oldPath) + relPath, err := filepath.Rel(sourceDir, path) if err != nil { return err } relPath = filepath.Join("/", relPath) - // Skip root if relPath == "/" { return nil } - change := Change{ - Path: relPath, + parent := root.LookUp(filepath.Dir(relPath)) + if parent == nil { + return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) } - newPath := filepath.Join(newDir, relPath) - - var newStat = &syscall.Stat_t{} - err = syscall.Lstat(newPath, newStat) - if err != nil && os.IsNotExist(err) { - change.Kind = ChangeDelete - changes = append(changes, change) + info := &FileInfo { + name: filepath.Base(relPath), + children: make(map[string]*FileInfo), + parent: parent, } + if err := syscall.Lstat(path, &info.stat); err != nil { + return err + } + + parent.children[info.name] = info + return nil }) if err != nil { return nil, err } - return changes, nil + return root, nil +} + +func ChangesDirs(newDir, oldDir string) ([]Change, error) { + oldRoot, err := collectFileInfo(oldDir) + if err != nil { + return nil, err + } + newRoot, err := collectFileInfo(newDir) + if err != nil { + return nil, err + } + + // Ignore changes in .docker-id + _ = newRoot.Remove("/.docker-id") + _ = oldRoot.Remove("/.docker-id") + + return newRoot.Changes(oldRoot), nil } From 99c7d129f422b488f478bc7887f37003dacc83e6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 15:45:58 +0200 Subject: [PATCH 038/126] Image.applyLayer: Be better at creating identical files There are some changes here that make the file metadata better match the layer files: * Set the mode of the file after the chown, as otherwise the per-group/uid specific flags and e.g. sticky bit is lost * Use lchown instead of chown * Delay mtime updates to after all other changes so that later file creation doesn't change the mtime for the parent directory * Use Futimes in combination with O_PATH|O_NOFOLLOW to set mtime on symlinks --- image.go | 55 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/image.go b/image.go index 1a279654d5..61ca83b208 100644 --- a/image.go +++ b/image.go @@ -175,7 +175,13 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) { return Tar(layerPath, compression) } +type TimeUpdate struct { + path string + time []syscall.Timeval +} + func (image *Image) applyLayer(layer, target string) error { + var updateTimes []TimeUpdate oldmask := syscall.Umask(0) defer syscall.Umask(oldmask) err := filepath.Walk(layer, func(srcPath string, f os.FileInfo, err error) error { @@ -249,11 +255,6 @@ func (image *Image) applyLayer(layer, target string) error { if err != nil { return err } - } else if srcStat.Mode&07777 != targetStat.Mode&07777 { - err = syscall.Chmod(targetPath, srcStat.Mode&07777) - if err != nil { - return err - } } } else if srcStat.Mode&syscall.S_IFLNK == syscall.S_IFLNK { // Source is symlink @@ -293,22 +294,52 @@ func (image *Image) applyLayer(layer, target string) error { return fmt.Errorf("Unknown type for file %s", srcPath) } + err = syscall.Lchown(targetPath, int(srcStat.Uid), int(srcStat.Gid)) + if err != nil { + return err + } + if srcStat.Mode&syscall.S_IFLNK != syscall.S_IFLNK { - err = syscall.Chown(targetPath, int(srcStat.Uid), int(srcStat.Gid)) + err = syscall.Chmod(targetPath, srcStat.Mode&07777) if err != nil { return err } - ts := []syscall.Timeval{ - syscall.NsecToTimeval(srcStat.Atim.Nano()), - syscall.NsecToTimeval(srcStat.Mtim.Nano()), - } - syscall.Utimes(targetPath, ts) } + ts := []syscall.Timeval{ + syscall.NsecToTimeval(srcStat.Atim.Nano()), + syscall.NsecToTimeval(srcStat.Mtim.Nano()), + } + + u := TimeUpdate { + path: targetPath, + time: ts, + } + + // Delay time updates until all other changes done, or it is + // overwritten for directories (by child changes) + updateTimes = append(updateTimes, u) } return nil }) - return err + if err != nil { + return err + } + + // We do this in reverse order so that children are updated before parents + for i := len(updateTimes) - 1; i >= 0; i-- { + update := updateTimes[i] + + O_PATH := 010000000 // Not in syscall yet + fd, err := syscall.Open(update.path, syscall.O_RDWR | O_PATH | syscall.O_NOFOLLOW, 0600) + if err != nil { + return err + } + syscall.Futimes(fd, update.time) + _ = syscall.Close(fd) + } + + return nil } func (image *Image) ensureImageDevice(devices DeviceSet) error { From 36603e68e33dd5ab5c317c181e023f7ef7356434 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 15:49:57 +0200 Subject: [PATCH 039/126] Changes: Better metadata comparison Change the comparison to better handle files that are copied during container creation but not actually changed: * Inode - this will change during a copy * ctime - this will change during a copy (as we can't set it back) * blocksize - this will change for sparse files during copy * size for directories - this can change anytime but doesn't necessarily reflect an actual contents change * Compare mtimes at microsecond precision (as this is what utimes has) --- changes.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/changes.go b/changes.go index d1b0a25b0d..53fa2d49b4 100644 --- a/changes.go +++ b/changes.go @@ -185,15 +185,22 @@ func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { // change? oldStat := &oldChild.stat newStat := &newChild.stat - if oldStat.Ino != newStat.Ino || - oldStat.Mode != newStat.Mode || + // Note: We can't compare inode or ctime or blocksize here, because these change + // when copying a file into a container. However, that is not generally a problem + // because any content change will change mtime, and any status change should + // be visible when actually comparing the stat fields. The only time this + // breaks down is if some code intentionally hides a change by setting + // back mtime + oldMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano()) + newMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano()) + if oldStat.Mode != newStat.Mode || oldStat.Uid != newStat.Uid || oldStat.Gid != newStat.Gid || oldStat.Rdev != newStat.Rdev || - oldStat.Size != newStat.Size || - oldStat.Blocks != newStat.Blocks || - oldStat.Mtim != newStat.Mtim || - oldStat.Ctim != newStat.Ctim { + // Don't look at size for dirs, its not a good measure of change + (oldStat.Size != newStat.Size && oldStat.Mode &syscall.S_IFDIR != syscall.S_IFDIR) || + oldMtime.Sec != newMtime.Sec || + oldMtime.Usec != newMtime.Usec { change := Change{ Path: newChild.path(), Kind: ChangeModify, From ad0a6a03e3595aa04cf731cf17e90be87163389a Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 15:56:06 +0200 Subject: [PATCH 040/126] Add Changes.ChangesLayers() This calculates the difference between a set of layers and a directory tree. --- changes.go | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/changes.go b/changes.go index 53fa2d49b4..49802d3170 100644 --- a/changes.go +++ b/changes.go @@ -235,11 +235,88 @@ func (info *FileInfo)Changes(oldInfo *FileInfo) []Change { } -func collectFileInfo(sourceDir string) (*FileInfo, error) { +func newRootFileInfo() *FileInfo { root := &FileInfo { name: "/", children: make(map[string]*FileInfo), } + return root +} + +func applyLayer(root *FileInfo, layer string) error { + err := filepath.Walk(layer, func(layerPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + // Skip root + if layerPath == layer { + return nil + } + + // rebase path + relPath, err := filepath.Rel(layer, layerPath) + if err != nil { + return err + } + relPath = filepath.Join("/", relPath) + + // Skip AUFS metadata + if matched, err := filepath.Match("/.wh..wh.*", relPath); err != nil || matched { + if err != nil || !f.IsDir() { + return err + } + return filepath.SkipDir + } + + var layerStat syscall.Stat_t + err = syscall.Lstat(layerPath, &layerStat) + if err != nil { + return err + } + + file := filepath.Base(relPath) + // If there is a whiteout, then the file was removed + if strings.HasPrefix(file, ".wh.") { + originalFile := file[len(".wh."):] + deletePath := filepath.Join(filepath.Dir(relPath), originalFile) + + root.Remove(deletePath) + } else { + // Added or changed file + existing := root.LookUp(relPath) + if existing != nil { + // Changed file + existing.stat = layerStat + if !existing.isDir() { + // Changed from dir to non-dir, delete all previous files + existing.children = make(map[string]*FileInfo) + } + } else { + // Added file + parent := root.LookUp(filepath.Dir(relPath)) + if parent == nil { + return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) + } + + info := &FileInfo { + name: filepath.Base(relPath), + children: make(map[string]*FileInfo), + parent: parent, + stat: layerStat, + } + + parent.children[info.name] = info + } + } + return nil + }) + return err +} + + +func collectFileInfo(sourceDir string) (*FileInfo, error) { + root := newRootFileInfo() err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error { if err != nil { @@ -282,6 +359,22 @@ func collectFileInfo(sourceDir string) (*FileInfo, error) { return root, nil } +func ChangesLayers(newDir string, layers []string) ([]Change, error) { + newRoot, err := collectFileInfo(newDir) + if err != nil { + return nil, err + } + oldRoot := newRootFileInfo() + for i := len(layers)-1; i >= 0; i-- { + layer := layers[i] + if err = applyLayer(oldRoot, layer); err != nil { + return nil, err + } + } + + return newRoot.Changes(oldRoot), nil +} + func ChangesDirs(newDir, oldDir string) ([]Change, error) { oldRoot, err := collectFileInfo(oldDir) if err != nil { From adae6849871fad0d74945fa1731712ea784e9a88 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 15:58:14 +0200 Subject: [PATCH 041/126] Add trivial copy-based CoW backend This creates a container by copying the corresponding files from the layers into the containers. This is not gonna be very useful on a developer setup, as there is no copy-on-write or general diskspace sharing. It also makes container instantiation slower. However, it may be useful in deployment where we don't always have a lot of containers running (long-running daemons) and where we don't do a lot of docker commits. --- container.go | 6 ++++- image.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++---- runtime.go | 2 ++ 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/container.go b/container.go index 549449080c..35cd11080f 100644 --- a/container.go +++ b/container.go @@ -1208,7 +1208,11 @@ func (container *Container) GetImage() (*Image, error) { } func (container *Container) Mounted() (bool, error) { - return Mounted(container.RootfsPath()) + image, err := container.GetImage() + if err != nil { + return false, err + } + return image.Mounted(container.runtime, container.RootfsPath(), container.rwPath()) } func (container *Container) Unmount() error { diff --git a/image.go b/image.go index 61ca83b208..2c16aeb6d4 100644 --- a/image.go +++ b/image.go @@ -442,16 +442,38 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return nil } +func (image *Image) Mounted(runtime *Runtime, root, rw string) (bool, error) { + method := runtime.GetMountMethod() + if method == MountMethodFilesystem { + if _, err := os.Stat(rw); err != nil { + if os.IsNotExist(err) { + err = nil + } + return false, err + } + mountedPath := path.Join(rw, ".fs-mounted") + if _, err := os.Stat(mountedPath); err != nil { + if os.IsNotExist(err) { + err = nil + } + return false, err + } + return true, nil + } else { + return Mounted(root) + } +} + func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { - if mounted, err := Mounted(root); err != nil { - return err - } else if mounted { + if mounted, _ := image.Mounted(runtime, root, rw); mounted { return fmt.Errorf("%s is already mounted", root) } + // Create the target directories if they don't exist if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) { return err } + switch runtime.GetMountMethod() { case MountMethodNone: return fmt.Errorf("No supported Mount implementation") @@ -502,6 +524,29 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { } } + case MountMethodFilesystem: + if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { + return err + } + + layers, err := image.layers() + if err != nil { + return err + } + + for i := len(layers)-1; i >= 0; i-- { + layer := layers[i] + if err = image.applyLayer(layer, root); err != nil { + return err + } + } + + mountedPath := path.Join(rw, ".fs-mounted") + fo, err := os.Create(mountedPath) + if err != nil { + return err + } + fo.Close() } return nil } @@ -527,7 +572,11 @@ func (image *Image) Unmount(runtime *Runtime, root string, id string) error { } return devices.DeactivateDevice(id) + + case MountMethodFilesystem: + return nil } + return nil } @@ -563,6 +612,17 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er return nil, err } return changes, nil + + case MountMethodFilesystem: + layers, err := image.layers() + if err != nil { + return nil, err + } + changes, err := ChangesLayers(root, layers) + if err != nil { + return nil, err + } + return changes, nil } return nil, fmt.Errorf("No supported Changes implementation") @@ -573,7 +633,7 @@ func (image *Image) ExportChanges(runtime *Runtime, root, rw, id string) (Archiv case MountMethodAUFS: return Tar(rw, Uncompressed) - case MountMethodDeviceMapper: + case MountMethodFilesystem, MountMethodDeviceMapper: changes, err := image.Changes(runtime, root, rw, id) if err != nil { return nil, err diff --git a/runtime.go b/runtime.go index 54071e96a9..52188f1384 100644 --- a/runtime.go +++ b/runtime.go @@ -23,6 +23,7 @@ const ( MountMethodNone MountMethod = iota MountMethodAUFS MountMethodDeviceMapper + MountMethodFilesystem ) type Capabilities struct { @@ -124,6 +125,7 @@ func (runtime *Runtime) GetMountMethod() MountMethod { runtime.mountMethod = MountMethodDeviceMapper } } + runtime.mountMethod = MountMethodFilesystem } return runtime.mountMethod From 5415804c9d7af6c75a147252ac17f71b9c3f0069 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 16:04:07 +0200 Subject: [PATCH 042/126] Remove accidental commit that enabled MountMethodFilesystem --- runtime.go | 1 - 1 file changed, 1 deletion(-) diff --git a/runtime.go b/runtime.go index 52188f1384..2a0dbe6a3e 100644 --- a/runtime.go +++ b/runtime.go @@ -125,7 +125,6 @@ func (runtime *Runtime) GetMountMethod() MountMethod { runtime.mountMethod = MountMethodDeviceMapper } } - runtime.mountMethod = MountMethodFilesystem } return runtime.mountMethod From cda87540135a85bd8d45d4eb4853d6962114ec55 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 16:35:56 +0200 Subject: [PATCH 043/126] Add CopyFile that can use btrfs reflinks if availible --- utils.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/utils.go b/utils.go index 57737f7b25..44573927a1 100644 --- a/utils.go +++ b/utils.go @@ -1,9 +1,33 @@ package docker +/* +#include +#include +#include + +// See linux.git/fs/btrfs/ioctl.h +#define BTRFS_IOCTL_MAGIC 0x94 +#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int) + +int +btrfs_reflink(int fd_out, int fd_in) +{ + int res; + res = ioctl(fd_out, BTRFS_IOC_CLONE, fd_in); + if (res < 0) + return errno; + return 0; +} + +*/ +import "C" import ( "fmt" + "io" "io/ioutil" + "os" "strings" + "syscall" ) // Compare two Config struct. Do not compare the "Image" nor "Hostname" fields @@ -182,3 +206,22 @@ func RootIsShared() bool { // No idea, probably safe to assume so return true } + +func BtrfsReflink(fd_out, fd_in uintptr) error { + res := C.btrfs_reflink(C.int(fd_out), C.int(fd_in)) + if res != 0 { + return syscall.Errno(res) + } + return nil +} + +func CopyFile(dstFile, srcFile *os.File) error { + err := BtrfsReflink(dstFile.Fd(), srcFile.Fd()) + if err == nil { + return nil + } + + // Fall back to normal copy + _, err = io.Copy(dstFile, srcFile) + return err +} From 062a2b32e9500107841a52d3a63b9232a1cfde70 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 16:36:32 +0200 Subject: [PATCH 044/126] applyLayer() use btrfs reflinks if availible We use the new file copy helper which uses btrfs reflinks if availible. --- image.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/image.go b/image.go index 2c16aeb6d4..7819873757 100644 --- a/image.go +++ b/image.go @@ -284,12 +284,16 @@ func (image *Image) applyLayer(layer, target string) error { } dstFile := os.NewFile(uintptr(fd), targetPath) srcFile, err := os.Open(srcPath) - _, err = io.Copy(dstFile, srcFile) + if err != nil { + _ = dstFile.Close() + return err + } + err = CopyFile(dstFile, srcFile) + _ = dstFile.Close() + _ = srcFile.Close() if err != nil { return err } - _ = srcFile.Close() - _ = dstFile.Close() } else { return fmt.Errorf("Unknown type for file %s", srcPath) } From cc28829429f5f11da287ecb75ee5b3e5f05d31ad Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 14:39:47 +0200 Subject: [PATCH 045/126] devmapper: Fix loopback mount code Typo in the loop-control code made it always fall back to the old method of opening loopback devices. --- devmapper/devmapper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devmapper/devmapper.go b/devmapper/devmapper.go index f007091827..75742628a2 100644 --- a/devmapper/devmapper.go +++ b/devmapper/devmapper.go @@ -27,7 +27,7 @@ attach_loop_device(const char *filename, int *loop_fd_out) start_index = 0; fd = open("/dev/loop-control", O_RDONLY); - if (fd == 0) { + if (fd >= 0) { start_index = ioctl(fd, LOOP_CTL_GET_FREE); close(fd); From ed658156133862b3f181c9d3061be24b91435095 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 14:57:22 +0200 Subject: [PATCH 046/126] image: Handle systems that don't support O_PATH when updating timestamp Older kernel can't handle O_PATH in open() so this will fail on dirs and symlinks. For dirs wa can fallback to the normal Utimes, but for symlinks there is not much to do but ignore their timestamps. --- image.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/image.go b/image.go index 7819873757..b3e8de3e55 100644 --- a/image.go +++ b/image.go @@ -336,11 +336,21 @@ func (image *Image) applyLayer(layer, target string) error { O_PATH := 010000000 // Not in syscall yet fd, err := syscall.Open(update.path, syscall.O_RDWR | O_PATH | syscall.O_NOFOLLOW, 0600) - if err != nil { - return err + if err == syscall.EISDIR || err == syscall.ELOOP { + // O_PATH not supported, use Utimes except on symlinks where Utimes doesn't work + if err != syscall.ELOOP { + err = syscall.Utimes(update.path, update.time) + if err != nil { + return err + } + } + } else { + if err != nil { + return err + } + syscall.Futimes(fd, update.time) + _ = syscall.Close(fd) } - syscall.Futimes(fd, update.time) - _ = syscall.Close(fd) } return nil From 009d0f9d81bbd5e130520986ce84e8f097d88a52 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 14:59:27 +0200 Subject: [PATCH 047/126] Image: unmount device before removing it on failures If we don't do this the remove will fail due to EBUSY --- image.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/image.go b/image.go index b3e8de3e55..16abf9eef0 100644 --- a/image.go +++ b/image.go @@ -414,6 +414,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { err = ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600) if err != nil { + _ = devices.UnmountDevice(image.ID, mountDir) _ = devices.RemoveDevice(image.ID) return err } @@ -432,11 +433,13 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { // part of the container changes dockerinitLayer, err := image.getDockerInitLayer() if err != nil { + _ = devices.UnmountDevice(image.ID, mountDir) _ = devices.RemoveDevice(image.ID) return err } err = image.applyLayer(dockerinitLayer, mountDir) if err != nil { + _ = devices.UnmountDevice(image.ID, mountDir) _ = devices.RemoveDevice(image.ID) return err } From f99f39abaa5a43e9abc19add5b9d1253a1d22485 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 15:44:43 +0200 Subject: [PATCH 048/126] ShellQuoteArguments: Fix quoting This accidentally used two quotes to start/end each quoted string. --- utils/utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/utils.go b/utils/utils.go index 94aa0dc902..6daac53a03 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1029,7 +1029,7 @@ func quote(word string, buf *bytes.Buffer) { return } - buf.WriteString("''") + buf.WriteString("'") for i := 0; i < len(word); i++ { b := word[i] @@ -1041,7 +1041,7 @@ func quote(word string, buf *bytes.Buffer) { } } - buf.WriteString("''") + buf.WriteString("'") } func ShellQuoteArguments(args []string) string { From 67788723c99cda8b41e5a488b988e2a72732d684 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 19:20:05 +0200 Subject: [PATCH 049/126] runtime test: Ensure all containers are unmounted at nuke() Otherwise we may leave around e.g. devmapper mounts --- container.go | 9 +++++++++ runtime_test.go | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/container.go b/container.go index 35cd11080f..f5f7d0ef80 100644 --- a/container.go +++ b/container.go @@ -1180,6 +1180,15 @@ func (container *Container) EnsureMounted() error { return container.Mount() } +func (container *Container) EnsureUnmounted() error { + if mounted, err := container.Mounted(); err != nil { + return err + } else if !mounted { + return nil + } + return container.Unmount() +} + func (container *Container) Mount() error { image, err := container.GetImage() if err != nil { diff --git a/runtime_test.go b/runtime_test.go index ccdf4d9563..684ca005cd 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -44,6 +44,10 @@ func nuke(runtime *Runtime) error { }(container) } wg.Wait() + + for _, container := range runtime.List() { + container.EnsureUnmounted() + } return os.RemoveAll(runtime.root) } From 41399ac005caa4de2dbb744e48f058f6a15e2d2b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 19:22:23 +0200 Subject: [PATCH 050/126] image: Unmount before removing device in error paths The device remove fails unless we unmount first --- image.go | 1 + 1 file changed, 1 insertion(+) diff --git a/image.go b/image.go index 16abf9eef0..f495e8e8fc 100644 --- a/image.go +++ b/image.go @@ -421,6 +421,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { err = image.applyLayer(layerPath(root), mountDir) if err != nil { + _ = devices.UnmountDevice(image.ID, mountDir) _ = devices.RemoveDevice(image.ID) return err } From 395bce4c4174014cb3264c35a7c6f97a2cb0948f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 20:38:06 +0200 Subject: [PATCH 051/126] Add DeviceSet.HasActivatedDevice() This lets you see if a device has been activated --- deviceset.go | 5 +++++ devmapper/deviceset_devmapper.go | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/deviceset.go b/deviceset.go index 5dd608361f..cf422acb3a 100644 --- a/deviceset.go +++ b/deviceset.go @@ -9,6 +9,7 @@ type DeviceSet interface { UnmountDevice(hash, path string) error HasDevice(hash string) bool HasInitializedDevice(hash string) bool + HasActivatedDevice(hash string) bool Shutdown() error } @@ -61,6 +62,10 @@ func (wrapper *DeviceSetWrapper) HasInitializedDevice(hash string) bool { return wrapper.wrapped.HasInitializedDevice(wrapper.wrap(hash)) } +func (wrapper *DeviceSetWrapper) HasActivatedDevice(hash string) bool { + return wrapper.wrapped.HasActivatedDevice(wrapper.wrap(hash)) +} + func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { wrapper := &DeviceSetWrapper{ wrapped: wrapped, diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go index 670d7621c4..bbf1fa6adc 100644 --- a/devmapper/deviceset_devmapper.go +++ b/devmapper/deviceset_devmapper.go @@ -851,6 +851,23 @@ func (devices *DeviceSetDM) HasInitializedDevice(hash string) bool { return info != nil && info.Initialized } +func (devices *DeviceSetDM) HasActivatedDevice(hash string) bool { + if err := devices.ensureInit(); err != nil { + return false + } + + info := devices.Devices[hash] + if info == nil { + return false + } + name := info.Name() + devinfo, _ := devices.getInfo(name) + if devinfo != nil && devinfo.Exists != 0 { + return true + } + return false +} + func (devices *DeviceSetDM) SetInitialized(hash string) error { if err := devices.ensureInit(); err != nil { return err From 6c7ae06435d6e288024691f1133d7a2a24fd8ef3 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 19:23:35 +0200 Subject: [PATCH 052/126] Image.Changes: Deactivate image device after unmounting it There is no need to keep the image device around if we were the onces creating the device. --- image.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/image.go b/image.go index f495e8e8fc..ff8b836c39 100644 --- a/image.go +++ b/image.go @@ -617,6 +617,8 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er return nil, err } + wasActivated := devices.HasActivatedDevice(image.ID) + // We re-use rw for the temporary mount of the base image as its // not used by device-mapper otherwise err = devices.MountDevice(image.ID, rw) @@ -626,6 +628,9 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er changes, err := ChangesDirs(root, rw) _ = devices.UnmountDevice(image.ID, rw) + if !wasActivated { + _ = devices.DeactivateDevice(image.ID) + } if err != nil { return nil, err } From 03320f0d1c5a687ec46cbf6d836cc91576b3b225 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 20:32:11 +0200 Subject: [PATCH 053/126] Tests: Clean up any old devmapper leftovers before starting tests --- devmapper/devmapper.go | 17 +++++++++++++++++ runtime_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/devmapper/devmapper.go b/devmapper/devmapper.go index 75742628a2..8458cb3ed9 100644 --- a/devmapper/devmapper.go +++ b/devmapper/devmapper.go @@ -347,3 +347,20 @@ func UdevWait(cookie uint32) error { func LogInitVerbose(level int) { C.dm_log_init_verbose(C.int(level)) } + +// Useful helper for cleanup +func RemoveDevice(name string) error { + task := TaskCreate(DeviceRemove) + if task == nil { + return fmt.Errorf("Can't create task of type DeviceRemove") + } + err := task.SetName(name) + if err != nil { + return fmt.Errorf("Can't set task name %s", name) + } + err = task.Run() + if err != nil { + return fmt.Errorf("Error running removeDevice") + } + return nil +} diff --git a/runtime_test.go b/runtime_test.go index 684ca005cd..bcbdf3f384 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -6,6 +6,7 @@ import ( "github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/devmapper" "io" + "io/ioutil" "log" "net" "os" @@ -84,6 +85,32 @@ func layerArchive(tarfile string) (io.Reader, error) { return f, nil } +// Remove any leftover device mapper devices from earlier runs of the unit tests +func cleanupDevMapper() { + infos, _ := ioutil.ReadDir("/dev/mapper") + if infos != nil { + hasPool := false + for _, info := range infos { + name := info.Name() + if strings.HasPrefix(name, "docker-unit-tests-devices-") { + if name == "docker-unit-tests-devices-pool" { + hasPool = true + } else { + if err := devmapper.RemoveDevice(name); err != nil { + panic(fmt.Errorf("Unable to remove existing device %s: %s", name, err)) + } + } + } + // We need to remove the pool last as the other devices block it + if hasPool { + if err := devmapper.RemoveDevice("docker-unit-tests-devices-pool"); err != nil { + panic(fmt.Errorf("Unable to remove existing device docker-unit-tests-devices-pool: %s", name, err)) + } + } + } + } +} + func init() { os.Setenv("TEST", "1") @@ -99,6 +126,8 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge + cleanupDevMapper() + // Always start from a clean set of loopback mounts err := os.RemoveAll(unitTestStoreDevicesBase) if err != nil { From d263aa6ca916ba9141f341447a2387e7a6316717 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 24 Sep 2013 14:16:11 +0200 Subject: [PATCH 054/126] RootIsShared: Fix root detection Column 4 is the mount position, column 3 will not always be "/" for the root. On one of my system its "/root". --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index 44573927a1..99600d1882 100644 --- a/utils.go +++ b/utils.go @@ -197,7 +197,7 @@ func RootIsShared() bool { if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { for _, line := range strings.Split(string(data), "\n") { cols := strings.Split(line, " ") - if len(cols) >= 6 && cols[3] == "/" && cols[4] == "/" { + if len(cols) >= 6 && cols[4] == "/" { return strings.HasPrefix(cols[6], "shared") } } From c1e25d7273f7f520a0dbac675db4e7e26c8a4a9b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 24 Sep 2013 17:20:58 +0000 Subject: [PATCH 055/126] add a -mount-method flag --- docker/docker.go | 7 ++++--- runtime.go | 14 +++++++++++--- runtime_test.go | 4 ++-- server.go | 4 ++-- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/docker/docker.go b/docker/docker.go index 4dcc174d5e..1f4dcc1d86 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -36,6 +36,7 @@ func main() { flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.") flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") flDns := flag.String("dns", "", "Set custom dns servers") + flMountMethod := flag.String("mount-method", "", "Set the mount method to use, default is auto. [aufs, devicemapper, filesystem]") flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)} flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") flag.Parse() @@ -65,7 +66,7 @@ func main() { flag.Usage() return } - if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil { + if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns, *flMountMethod); err != nil { log.Fatal(err) os.Exit(-1) } @@ -116,7 +117,7 @@ func removePidFile(pidfile string) { } } -func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error { +func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns, mountMethod string) error { if err := createPidFile(pidfile); err != nil { log.Fatal(err) } @@ -134,7 +135,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart if flDns != "" { dns = []string{flDns} } - server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns) + server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns, mountMethod) if err != nil { return err } diff --git a/runtime.go b/runtime.go index 2a0dbe6a3e..57acdf0fb5 100644 --- a/runtime.go +++ b/runtime.go @@ -518,8 +518,8 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a } // FIXME: harmonize with NewGraph() -func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string) (*Runtime, error) { - runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart) +func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string, mountMethod string) (*Runtime, error) { + runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart, mountMethod) if err != nil { return nil, err } @@ -537,7 +537,7 @@ func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns [ return runtime, nil } -func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) (*Runtime, error) { +func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool, mountMethod string) (*Runtime, error) { runtimeRepo := path.Join(root, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { @@ -577,6 +577,14 @@ func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) deviceSet: deviceSet, } + if mountMethod == "aufs" { + runtime.mountMethod = MountMethodAUFS + } else if mountMethod == "devicemapper" { + runtime.mountMethod = MountMethodDeviceMapper + } else if mountMethod == "filesystem" { + runtime.mountMethod = MountMethodFilesystem + } + if err := runtime.restore(); err != nil { return nil, err } diff --git a/runtime_test.go b/runtime_test.go index bcbdf3f384..b4be93fc1e 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -135,7 +135,7 @@ func init() { } // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false, ""); err != nil { panic(err) } else { globalRuntime = runtime @@ -504,7 +504,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false) + runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false, "") if err != nil { t.Fatal(err) } diff --git a/server.go b/server.go index 7f82d7adab..6b32d4e8fb 100644 --- a/server.go +++ b/server.go @@ -1294,11 +1294,11 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er } -func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { +func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts, mountMethod string) (*Server, error) { if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } - runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns) + runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns, mountMethod) if err != nil { return nil, err } From 5e1d540209342fa2d6e2ab7117062a897ccf8fe8 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 26 Sep 2013 15:08:26 +0000 Subject: [PATCH 056/126] Revert "add a -mount-method flag" This reverts commit e52d756f40c9ccf8b37ca496cb72be057c909ed7. --- docker/docker.go | 7 +++---- runtime.go | 14 +++----------- runtime_test.go | 4 ++-- server.go | 4 ++-- utils_test.go | 2 +- 5 files changed, 11 insertions(+), 20 deletions(-) diff --git a/docker/docker.go b/docker/docker.go index 1f4dcc1d86..4dcc174d5e 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -36,7 +36,6 @@ func main() { flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.") flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") flDns := flag.String("dns", "", "Set custom dns servers") - flMountMethod := flag.String("mount-method", "", "Set the mount method to use, default is auto. [aufs, devicemapper, filesystem]") flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)} flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") flag.Parse() @@ -66,7 +65,7 @@ func main() { flag.Usage() return } - if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns, *flMountMethod); err != nil { + if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil { log.Fatal(err) os.Exit(-1) } @@ -117,7 +116,7 @@ func removePidFile(pidfile string) { } } -func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns, mountMethod string) error { +func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error { if err := createPidFile(pidfile); err != nil { log.Fatal(err) } @@ -135,7 +134,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart if flDns != "" { dns = []string{flDns} } - server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns, mountMethod) + server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns) if err != nil { return err } diff --git a/runtime.go b/runtime.go index 57acdf0fb5..2a0dbe6a3e 100644 --- a/runtime.go +++ b/runtime.go @@ -518,8 +518,8 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a } // FIXME: harmonize with NewGraph() -func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string, mountMethod string) (*Runtime, error) { - runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart, mountMethod) +func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string) (*Runtime, error) { + runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart) if err != nil { return nil, err } @@ -537,7 +537,7 @@ func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns [ return runtime, nil } -func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool, mountMethod string) (*Runtime, error) { +func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) (*Runtime, error) { runtimeRepo := path.Join(root, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { @@ -577,14 +577,6 @@ func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool, deviceSet: deviceSet, } - if mountMethod == "aufs" { - runtime.mountMethod = MountMethodAUFS - } else if mountMethod == "devicemapper" { - runtime.mountMethod = MountMethodDeviceMapper - } else if mountMethod == "filesystem" { - runtime.mountMethod = MountMethodFilesystem - } - if err := runtime.restore(); err != nil { return nil, err } diff --git a/runtime_test.go b/runtime_test.go index b4be93fc1e..bcbdf3f384 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -135,7 +135,7 @@ func init() { } // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false, ""); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { panic(err) } else { globalRuntime = runtime @@ -504,7 +504,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false, "") + runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false) if err != nil { t.Fatal(err) } diff --git a/server.go b/server.go index 6b32d4e8fb..7f82d7adab 100644 --- a/server.go +++ b/server.go @@ -1294,11 +1294,11 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er } -func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts, mountMethod string) (*Server, error) { +func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } - runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns, mountMethod) + runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns) if err != nil { return nil, err } diff --git a/utils_test.go b/utils_test.go index f5bdac3271..87f67ff0d7 100644 --- a/utils_test.go +++ b/utils_test.go @@ -43,7 +43,7 @@ func newTestRuntime() (*Runtime, error) { return nil, err } - runtime, err := NewRuntimeFromDirectory(root, NewDeviceSetWrapper (globalRuntime.deviceSet, filepath.Base(root)), false) + runtime, err := NewRuntimeFromDirectory(root, NewDeviceSetWrapper(globalRuntime.deviceSet, filepath.Base(root)), false) if err != nil { return nil, err } From ebfa24acb08504d8da3fcba8da897fed357f34b2 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 26 Sep 2013 15:40:13 +0000 Subject: [PATCH 057/126] go fmt and aufs support removed --- api_params.go | 14 +-- api_test.go | 1 - archive.go | 8 +- changes.go | 116 ++++--------------- container.go | 4 +- deviceset.go | 5 +- image.go | 290 +++++++++++++----------------------------------- mount.go | 29 ----- runtime.go | 34 +----- runtime_test.go | 17 ++- utils_test.go | 2 +- 11 files changed, 124 insertions(+), 396 deletions(-) diff --git a/api_params.go b/api_params.go index 6403bc6a26..5f1a338057 100644 --- a/api_params.go +++ b/api_params.go @@ -56,13 +56,13 @@ type APIContainers struct { func (self *APIContainers) ToLegacy() APIContainersOld { return APIContainersOld{ - ID: self.ID, - Image: self.Image, - Command: self.Command, - Created: self.Created, - Status: self.Status, - Ports: displayablePorts(self.Ports), - SizeRw: self.SizeRw, + ID: self.ID, + Image: self.Image, + Command: self.Command, + Created: self.Created, + Status: self.Status, + Ports: displayablePorts(self.Ports), + SizeRw: self.SizeRw, SizeRootFs: self.SizeRootFs, } } diff --git a/api_test.go b/api_test.go index edff6788e1..b24aaa36f8 100644 --- a/api_test.go +++ b/api_test.go @@ -566,7 +566,6 @@ func TestPostCommit(t *testing.T) { srv := &Server{runtime: runtime} - // Create a container and remove a file container, err := runtime.Create( &Config{ diff --git a/archive.go b/archive.go index f3f7b8c59e..75b6e7e1f1 100644 --- a/archive.go +++ b/archive.go @@ -84,13 +84,13 @@ func Tar(path string, compression Compression) (io.Reader, error) { } func escapeName(name string) string { - escaped := make([]byte,0) + escaped := make([]byte, 0) for i, c := range []byte(name) { if i == 0 && c == '/' { continue } // all printable chars except "-" which is 0x2d - if (0x20 <= c && c <= 0x7E) && c != 0x2d { + if (0x20 <= c && c <= 0x7E) && c != 0x2d { escaped = append(escaped, c) } else { escaped = append(escaped, fmt.Sprintf("\\%03o", c)...) @@ -102,7 +102,7 @@ func escapeName(name string) string { // Tar creates an archive from the directory at `path`, only including files whose relative // paths are included in `filter`. If `filter` is nil, then all files are included. func TarFilter(path string, compression Compression, filter []string, recursive bool, createFiles []string) (io.Reader, error) { - args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-",} + args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-"} if filter == nil { filter = []string{"."} } @@ -142,7 +142,7 @@ func TarFilter(path string, compression Compression, filter []string, recursive } } - return CmdStream(exec.Command(args[0], args[1:]...), &files, func () { + return CmdStream(exec.Command(args[0], args[1:]...), &files, func() { if tmpDir != "" { _ = os.RemoveAll(tmpDir) } diff --git a/changes.go b/changes.go index 49802d3170..77bef6fb22 100644 --- a/changes.go +++ b/changes.go @@ -34,82 +34,10 @@ func (change *Change) String() string { return fmt.Sprintf("%s %s", kind, change.Path) } -func ChangesAUFS(layers []string, rw string) ([]Change, error) { - var changes []Change - err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error { - if err != nil { - return err - } - - // Rebase path - path, err = filepath.Rel(rw, path) - if err != nil { - return err - } - path = filepath.Join("/", path) - - // Skip root - if path == "/" { - return nil - } - - // Skip AUFS metadata - if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched { - return err - } - - change := Change{ - Path: path, - } - - // Find out what kind of modification happened - file := filepath.Base(path) - // If there is a whiteout, then the file was removed - if strings.HasPrefix(file, ".wh.") { - originalFile := file[len(".wh."):] - change.Path = filepath.Join(filepath.Dir(path), originalFile) - change.Kind = ChangeDelete - } else { - // Otherwise, the file was added - change.Kind = ChangeAdd - - // ...Unless it already existed in a top layer, in which case, it's a modification - for _, layer := range layers { - stat, err := os.Stat(filepath.Join(layer, path)) - if err != nil && !os.IsNotExist(err) { - return err - } - if err == nil { - // The file existed in the top layer, so that's a modification - - // However, if it's a directory, maybe it wasn't actually modified. - // If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar - if stat.IsDir() && f.IsDir() { - if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() { - // Both directories are the same, don't record the change - return nil - } - } - change.Kind = ChangeModify - break - } - } - } - - // Record change - changes = append(changes, change) - return nil - }) - if err != nil && !os.IsNotExist(err) { - return nil, err - } - return changes, nil -} - type FileInfo struct { - parent *FileInfo - name string - stat syscall.Stat_t + parent *FileInfo + name string + stat syscall.Stat_t children map[string]*FileInfo } @@ -132,20 +60,20 @@ func (root *FileInfo) LookUp(path string) *FileInfo { return parent } -func (info *FileInfo)path() string { +func (info *FileInfo) path() string { if info.parent == nil { return "/" } return filepath.Join(info.parent.path(), info.name) } -func (info *FileInfo)unlink() { +func (info *FileInfo) unlink() { if info.parent != nil { delete(info.parent.children, info.name) } } -func (info *FileInfo)Remove(path string) bool { +func (info *FileInfo) Remove(path string) bool { child := info.LookUp(path) if child != nil { child.unlink() @@ -154,12 +82,11 @@ func (info *FileInfo)Remove(path string) bool { return false } -func (info *FileInfo)isDir() bool { +func (info *FileInfo) isDir() bool { return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR } - -func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { +func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { if oldInfo == nil { // add change := Change{ @@ -198,7 +125,7 @@ func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { oldStat.Gid != newStat.Gid || oldStat.Rdev != newStat.Rdev || // Don't look at size for dirs, its not a good measure of change - (oldStat.Size != newStat.Size && oldStat.Mode &syscall.S_IFDIR != syscall.S_IFDIR) || + (oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) || oldMtime.Sec != newMtime.Sec || oldMtime.Usec != newMtime.Usec { change := Change{ @@ -223,10 +150,9 @@ func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { *changes = append(*changes, change) } - } -func (info *FileInfo)Changes(oldInfo *FileInfo) []Change { +func (info *FileInfo) Changes(oldInfo *FileInfo) []Change { var changes []Change info.addChanges(oldInfo, &changes) @@ -234,10 +160,9 @@ func (info *FileInfo)Changes(oldInfo *FileInfo) []Change { return changes } - func newRootFileInfo() *FileInfo { - root := &FileInfo { - name: "/", + root := &FileInfo{ + name: "/", children: make(map[string]*FileInfo), } return root @@ -299,11 +224,11 @@ func applyLayer(root *FileInfo, layer string) error { return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) } - info := &FileInfo { - name: filepath.Base(relPath), + info := &FileInfo{ + name: filepath.Base(relPath), children: make(map[string]*FileInfo), - parent: parent, - stat: layerStat, + parent: parent, + stat: layerStat, } parent.children[info.name] = info @@ -314,7 +239,6 @@ func applyLayer(root *FileInfo, layer string) error { return err } - func collectFileInfo(sourceDir string) (*FileInfo, error) { root := newRootFileInfo() @@ -339,10 +263,10 @@ func collectFileInfo(sourceDir string) (*FileInfo, error) { return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) } - info := &FileInfo { - name: filepath.Base(relPath), + info := &FileInfo{ + name: filepath.Base(relPath), children: make(map[string]*FileInfo), - parent: parent, + parent: parent, } if err := syscall.Lstat(path, &info.stat); err != nil { @@ -365,7 +289,7 @@ func ChangesLayers(newDir string, layers []string) ([]Change, error) { return nil, err } oldRoot := newRootFileInfo() - for i := len(layers)-1; i >= 0; i-- { + for i := len(layers) - 1; i >= 0; i-- { layer := layers[i] if err = applyLayer(oldRoot, layer); err != nil { return nil, err diff --git a/container.go b/container.go index f5f7d0ef80..6c24175fdc 100644 --- a/container.go +++ b/container.go @@ -803,10 +803,10 @@ func (container *Container) Start(hostConfig *HostConfig) error { // without exec in go we have to do this horrible shell hack... shellString := "mount --make-rprivate /; exec " + - utils.ShellQuoteArguments(params) + utils.ShellQuoteArguments(params) params = []string{ - "unshare", "-m", "--", "/bin/sh", "-c", shellString, + "unshare", "-m", "--", "/bin/sh", "-c", shellString, } } diff --git a/deviceset.go b/deviceset.go index cf422acb3a..8e619ca248 100644 --- a/deviceset.go +++ b/deviceset.go @@ -15,7 +15,7 @@ type DeviceSet interface { type DeviceSetWrapper struct { wrapped DeviceSet - prefix string + prefix string } func (wrapper *DeviceSetWrapper) wrap(hash string) string { @@ -25,7 +25,6 @@ func (wrapper *DeviceSetWrapper) wrap(hash string) string { return hash } - func (wrapper *DeviceSetWrapper) AddDevice(hash, baseHash string) error { return wrapper.wrapped.AddDevice(wrapper.wrap(hash), wrapper.wrap(baseHash)) } @@ -69,7 +68,7 @@ func (wrapper *DeviceSetWrapper) HasActivatedDevice(hash string) bool { func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { wrapper := &DeviceSetWrapper{ wrapped: wrapped, - prefix: prefix, + prefix: prefix, } return wrapper } diff --git a/image.go b/image.go index ff8b836c39..2187605d51 100644 --- a/image.go +++ b/image.go @@ -8,9 +8,7 @@ import ( "github.com/dotcloud/docker/utils" "io" "io/ioutil" - "log" "os" - "os/exec" "path" "path/filepath" "strconv" @@ -141,31 +139,6 @@ func mountPath(root string) string { return path.Join(root, "mount") } -func MountAUFS(ro []string, rw string, target string) error { - // FIXME: Now mount the layers - rwBranch := fmt.Sprintf("%v=rw", rw) - roBranches := "" - for _, layer := range ro { - roBranches += fmt.Sprintf("%v=ro+wh:", layer) - } - branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches) - - branches += ",xino=/dev/shm/aufs.xino" - - //if error, try to load aufs kernel module - if err := mount("none", target, "aufs", 0, branches); err != nil { - log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...") - if err := exec.Command("modprobe", "aufs").Run(); err != nil { - return fmt.Errorf("Unable to load the AUFS module") - } - log.Printf("...module loaded.") - if err := mount("none", target, "aufs", 0, branches); err != nil { - return fmt.Errorf("Unable to mount using aufs") - } - } - return nil -} - // TarLayer returns a tar archive of the image's filesystem layer. func (image *Image) TarLayer(compression Compression) (Archive, error) { layerPath, err := image.layer() @@ -315,7 +288,7 @@ func (image *Image) applyLayer(layer, target string) error { syscall.NsecToTimeval(srcStat.Mtim.Nano()), } - u := TimeUpdate { + u := TimeUpdate{ path: targetPath, time: ts, } @@ -335,7 +308,7 @@ func (image *Image) applyLayer(layer, target string) error { update := updateTimes[i] O_PATH := 010000000 // Not in syscall yet - fd, err := syscall.Open(update.path, syscall.O_RDWR | O_PATH | syscall.O_NOFOLLOW, 0600) + fd, err := syscall.Open(update.path, syscall.O_RDWR|O_PATH|syscall.O_NOFOLLOW, 0600) if err == syscall.EISDIR || err == syscall.ELOOP { // O_PATH not supported, use Utimes except on symlinks where Utimes doesn't work if err != syscall.ELOOP { @@ -411,7 +384,6 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } - err = ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600) if err != nil { _ = devices.UnmountDevice(image.ID, mountDir) @@ -461,25 +433,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { } func (image *Image) Mounted(runtime *Runtime, root, rw string) (bool, error) { - method := runtime.GetMountMethod() - if method == MountMethodFilesystem { - if _, err := os.Stat(rw); err != nil { - if os.IsNotExist(err) { - err = nil - } - return false, err - } - mountedPath := path.Join(rw, ".fs-mounted") - if _, err := os.Stat(mountedPath); err != nil { - if os.IsNotExist(err) { - err = nil - } - return false, err - } - return true, nil - } else { - return Mounted(root) - } + return Mounted(root) } func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { @@ -492,195 +446,107 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { return err } - switch runtime.GetMountMethod() { - case MountMethodNone: - return fmt.Errorf("No supported Mount implementation") + devices, err := runtime.GetDeviceSet() + if err != nil { + return err + } + err = image.ensureImageDevice(devices) + if err != nil { + return err + } - case MountMethodAUFS: - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return err - } - layers, err := image.layers() + createdDevice := false + if !devices.HasDevice(id) { + utils.Debugf("Creating device %s for container based on image %s", id, image.ID) + err = devices.AddDevice(id, image.ID) if err != nil { return err } - if err := MountAUFS(layers, rw, root); err != nil { - return err - } + createdDevice = true + } - case MountMethodDeviceMapper: - devices, err := runtime.GetDeviceSet() + utils.Debugf("Mounting container %s at %s for container", id, root) + err = devices.MountDevice(id, root) + if err != nil { + return err + } + + if createdDevice { + err = ioutil.WriteFile(path.Join(root, ".docker-id"), []byte(id), 0600) if err != nil { + _ = devices.RemoveDevice(image.ID) return err } - err = image.ensureImageDevice(devices) - if err != nil { - return err - } - - createdDevice := false - if !devices.HasDevice(id) { - utils.Debugf("Creating device %s for container based on image %s", id, image.ID) - err = devices.AddDevice(id, image.ID) - if err != nil { - return err - } - createdDevice = true - } - - utils.Debugf("Mounting container %s at %s for container", id, root) - err = devices.MountDevice(id, root) - if err != nil { - return err - } - - if createdDevice { - err = ioutil.WriteFile(path.Join(root, ".docker-id"), []byte(id), 0600) - if err != nil { - _ = devices.RemoveDevice(image.ID) - return err - } - } - - case MountMethodFilesystem: - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return err - } - - layers, err := image.layers() - if err != nil { - return err - } - - for i := len(layers)-1; i >= 0; i-- { - layer := layers[i] - if err = image.applyLayer(layer, root); err != nil { - return err - } - } - - mountedPath := path.Join(rw, ".fs-mounted") - fo, err := os.Create(mountedPath) - if err != nil { - return err - } - fo.Close() } return nil } func (image *Image) Unmount(runtime *Runtime, root string, id string) error { - switch runtime.GetMountMethod() { - case MountMethodNone: - return fmt.Errorf("No supported Unmount implementation") - - case MountMethodAUFS: - return Unmount(root) - - case MountMethodDeviceMapper: - // Try to deactivate the device as generally there is no use for it anymore - devices, err := runtime.GetDeviceSet() - if err != nil { - return err; - } - - err = devices.UnmountDevice(id, root) - if err != nil { - return err - } - - return devices.DeactivateDevice(id) - - case MountMethodFilesystem: - return nil + // Try to deactivate the device as generally there is no use for it anymore + devices, err := runtime.GetDeviceSet() + if err != nil { + return err } - return nil + err = devices.UnmountDevice(id, root) + if err != nil { + return err + } + + return devices.DeactivateDevice(id) } func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, error) { - switch runtime.GetMountMethod() { - case MountMethodAUFS: - layers, err := image.layers() - if err != nil { - return nil, err - } - return ChangesAUFS(layers, rw) - - case MountMethodDeviceMapper: - devices, err := runtime.GetDeviceSet() - if err != nil { - return nil, err - } - - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return nil, err - } - - wasActivated := devices.HasActivatedDevice(image.ID) - - // We re-use rw for the temporary mount of the base image as its - // not used by device-mapper otherwise - err = devices.MountDevice(image.ID, rw) - if err != nil { - return nil, err - } - - changes, err := ChangesDirs(root, rw) - _ = devices.UnmountDevice(image.ID, rw) - if !wasActivated { - _ = devices.DeactivateDevice(image.ID) - } - if err != nil { - return nil, err - } - return changes, nil - - case MountMethodFilesystem: - layers, err := image.layers() - if err != nil { - return nil, err - } - changes, err := ChangesLayers(root, layers) - if err != nil { - return nil, err - } - return changes, nil + devices, err := runtime.GetDeviceSet() + if err != nil { + return nil, err } - return nil, fmt.Errorf("No supported Changes implementation") + if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { + return nil, err + } + + wasActivated := devices.HasActivatedDevice(image.ID) + + // We re-use rw for the temporary mount of the base image as its + // not used by device-mapper otherwise + err = devices.MountDevice(image.ID, rw) + if err != nil { + return nil, err + } + + changes, err := ChangesDirs(root, rw) + _ = devices.UnmountDevice(image.ID, rw) + if !wasActivated { + _ = devices.DeactivateDevice(image.ID) + } + if err != nil { + return nil, err + } + return changes, nil } func (image *Image) ExportChanges(runtime *Runtime, root, rw, id string) (Archive, error) { - switch runtime.GetMountMethod() { - case MountMethodAUFS: - return Tar(rw, Uncompressed) - - case MountMethodFilesystem, MountMethodDeviceMapper: - changes, err := image.Changes(runtime, root, rw, id) - if err != nil { - return nil, err - } - - files := make([]string, 0) - deletions := make([]string, 0) - for _, change := range changes { - if change.Kind == ChangeModify || change.Kind == ChangeAdd { - files = append(files, change.Path) - } - if change.Kind == ChangeDelete { - base := filepath.Base(change.Path) - dir := filepath.Dir(change.Path) - deletions = append(deletions, filepath.Join(dir, ".wh."+base)) - } - } - - return TarFilter(root, Uncompressed, files, false, deletions) + changes, err := image.Changes(runtime, root, rw, id) + if err != nil { + return nil, err } - return nil, fmt.Errorf("No supported Changes implementation") -} + files := make([]string, 0) + deletions := make([]string, 0) + for _, change := range changes { + if change.Kind == ChangeModify || change.Kind == ChangeAdd { + files = append(files, change.Path) + } + if change.Kind == ChangeDelete { + base := filepath.Base(change.Path) + dir := filepath.Dir(change.Path) + deletions = append(deletions, filepath.Join(dir, ".wh."+base)) + } + } + return TarFilter(root, Uncompressed, files, false, deletions) +} func (image *Image) ShortID() string { return utils.TruncateID(image.ID) diff --git a/mount.go b/mount.go index 541c29c13a..3e2a21df50 100644 --- a/mount.go +++ b/mount.go @@ -1,40 +1,11 @@ package docker import ( - "fmt" - "github.com/dotcloud/docker/utils" "os" - "os/exec" "path/filepath" "syscall" - "time" ) -func Unmount(target string) error { - if err := exec.Command("auplink", target, "flush").Run(); err != nil { - utils.Debugf("[warning]: couldn't run auplink before unmount: %s", err) - } - if err := syscall.Unmount(target, 0); err != nil { - return err - } - // Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint - // for some time. We'll just keep retrying until it succeeds. - for retries := 0; retries < 1000; retries++ { - err := os.Remove(target) - if err == nil { - // rm mntpoint succeeded - return nil - } - if os.IsNotExist(err) { - // mntpoint doesn't exist anymore. Success. - return nil - } - // fmt.Printf("(%v) Remove %v returned: %v\n", retries, target, err) - time.Sleep(10 * time.Millisecond) - } - return fmt.Errorf("Umount: Failed to umount %v", target) -} - func Mounted(mountpoint string) (bool, error) { mntpoint, err := os.Stat(mountpoint) if err != nil { diff --git a/runtime.go b/runtime.go index 2a0dbe6a3e..a92b0e5f70 100644 --- a/runtime.go +++ b/runtime.go @@ -17,14 +17,6 @@ import ( ) var defaultDns = []string{"8.8.8.8", "8.8.4.4"} -type MountMethod int - -const ( - MountMethodNone MountMethod = iota - MountMethodAUFS - MountMethodDeviceMapper - MountMethodFilesystem -) type Capabilities struct { MemoryLimit bool @@ -47,7 +39,6 @@ type Runtime struct { srv *Server Dns []string deviceSet DeviceSet - mountMethod MountMethod } var sysInitPath string @@ -109,27 +100,6 @@ func hasFilesystemSupport(fstype string) bool { return false } -func (runtime *Runtime) GetMountMethod() MountMethod { - if runtime.mountMethod == MountMethodNone { - // Try to automatically pick a method - if hasFilesystemSupport("aufs") { - utils.Debugf("Using AUFS backend.") - runtime.mountMethod = MountMethodAUFS - } else { - _ = exec.Command("modprobe", "aufs").Run() - if hasFilesystemSupport("aufs") { - utils.Debugf("Using AUFS backend.") - runtime.mountMethod = MountMethodAUFS - } else { - utils.Debugf("Using device-mapper backend.") - runtime.mountMethod = MountMethodDeviceMapper - } - } - } - - return runtime.mountMethod -} - func (runtime *Runtime) GetDeviceSet() (DeviceSet, error) { if runtime.deviceSet == nil { return nil, fmt.Errorf("No device set available") @@ -288,7 +258,7 @@ func (runtime *Runtime) Destroy(container *Container) error { if err := os.RemoveAll(container.root); err != nil { return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err) } - if runtime.GetMountMethod() == MountMethodDeviceMapper && runtime.deviceSet.HasDevice(container.ID) { + if runtime.deviceSet.HasDevice(container.ID) { if err := runtime.deviceSet.RemoveDevice(container.ID); err != nil { return fmt.Errorf("Unable to remove device for %v: %v", container.ID, err) } @@ -301,7 +271,7 @@ func (runtime *Runtime) DeleteImage(id string) error { if err != nil { return err } - if runtime.GetMountMethod() == MountMethodDeviceMapper && runtime.deviceSet.HasDevice(id) { + if runtime.deviceSet.HasDevice(id) { if err := runtime.deviceSet.RemoveDevice(id); err != nil { return fmt.Errorf("Unable to remove device for %v: %v", id, err) } diff --git a/runtime_test.go b/runtime_test.go index bcbdf3f384..0a9b95a411 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -3,8 +3,8 @@ package docker import ( "bytes" "fmt" - "github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/devmapper" + "github.com/dotcloud/docker/utils" "io" "io/ioutil" "log" @@ -20,13 +20,13 @@ import ( ) const ( - unitTestImageName = "docker-test-image" - unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 - unitTestNetworkBridge = "testdockbr0" - unitTestStoreBase = "/var/lib/docker/unit-tests" - unitTestStoreDevicesBase = "/var/lib/docker/unit-tests-devices" - testDaemonAddr = "127.0.0.1:4270" - testDaemonProto = "tcp" + unitTestImageName = "docker-test-image" + unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 + unitTestNetworkBridge = "testdockbr0" + unitTestStoreBase = "/var/lib/docker/unit-tests" + unitTestStoreDevicesBase = "/var/lib/docker/unit-tests-devices" + testDaemonAddr = "127.0.0.1:4270" + testDaemonProto = "tcp" ) var ( @@ -75,7 +75,6 @@ func cleanupLast(runtime *Runtime) error { return nil } - func layerArchive(tarfile string) (io.Reader, error) { // FIXME: need to close f somewhere f, err := os.Open(tarfile) diff --git a/utils_test.go b/utils_test.go index 87f67ff0d7..f458b1da91 100644 --- a/utils_test.go +++ b/utils_test.go @@ -2,11 +2,11 @@ package docker import ( "github.com/dotcloud/docker/utils" - "path/filepath" "io" "io/ioutil" "os" "path" + "path/filepath" "strings" "testing" ) From 75e958bf48a83de5f3f80859aee96f3356d16d4b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 26 Sep 2013 21:12:12 +0200 Subject: [PATCH 058/126] Image: Fix time setting for old kernels This is a better fix for futimes() on kernels not supporting O_PATH. The previous fix broke when copying a device, as it tried to open it and got and error. --- image.go | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/image.go b/image.go index 2187605d51..06036e1e62 100644 --- a/image.go +++ b/image.go @@ -151,6 +151,7 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) { type TimeUpdate struct { path string time []syscall.Timeval + mode uint32 } func (image *Image) applyLayer(layer, target string) error { @@ -291,6 +292,7 @@ func (image *Image) applyLayer(layer, target string) error { u := TimeUpdate{ path: targetPath, time: ts, + mode: srcStat.Mode, } // Delay time updates until all other changes done, or it is @@ -308,21 +310,24 @@ func (image *Image) applyLayer(layer, target string) error { update := updateTimes[i] O_PATH := 010000000 // Not in syscall yet - fd, err := syscall.Open(update.path, syscall.O_RDWR|O_PATH|syscall.O_NOFOLLOW, 0600) - if err == syscall.EISDIR || err == syscall.ELOOP { - // O_PATH not supported, use Utimes except on symlinks where Utimes doesn't work - if err != syscall.ELOOP { - err = syscall.Utimes(update.path, update.time) - if err != nil { - return err - } + var err error = nil + if update.mode&syscall.S_IFLNK == syscall.S_IFLNK { + // Update time on the symlink via O_PATH + futimes(), if supported by the kernel + + fd, err := syscall.Open(update.path, syscall.O_RDWR|O_PATH|syscall.O_NOFOLLOW, 0600) + if err == syscall.EISDIR || err == syscall.ELOOP { + // O_PATH not supported by kernel, nothing to do, ignore + } else if err != nil { + return err + } else { + syscall.Futimes(fd, update.time) + _ = syscall.Close(fd) } } else { + err = syscall.Utimes(update.path, update.time) if err != nil { return err } - syscall.Futimes(fd, update.time) - _ = syscall.Close(fd) } } From 459bac712709db4d188022539f4d59524b4b3670 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:14:31 +0200 Subject: [PATCH 059/126] Add libdevmapper wrapper --- devmapper/devmapper.go | 349 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 349 insertions(+) create mode 100644 devmapper/devmapper.go diff --git a/devmapper/devmapper.go b/devmapper/devmapper.go new file mode 100644 index 0000000000..f007091827 --- /dev/null +++ b/devmapper/devmapper.go @@ -0,0 +1,349 @@ +package devmapper + +/* +#cgo LDFLAGS: -L. -ldevmapper +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static 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) { + 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) || !S_ISBLK(st.st_mode)) { + close(fd); + return NULL; + } + + loop_fd = open(buf, O_RDWR); + if (loop_fd < 0 && errno == ENOENT) { + close(fd); + fprintf (stderr, "no available loopback device!"); + return NULL; + } else if (loop_fd < 0) + continue; + + if (ioctl (loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) { + close(loop_fd); + loop_fd = -1; + if (errno != EBUSY) { + close (fd); + fprintf (stderr, "cannot set up loopback device %s", buf); + 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) { + ioctl(loop_fd, LOOP_CLR_FD, 0); + 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; +} + +static int64_t +get_block_size(int fd) +{ + uint64_t size; + if (ioctl(fd, BLKGETSIZE64, &size) == -1) + return -1; + return (int64_t)size; +} + + +*/ +import "C" +import "unsafe" +import "fmt" +import "runtime" +import "os" + +func SetDevDir(dir string) error { + c_dir := C.CString(dir) + defer C.free(unsafe.Pointer(c_dir)) + res := C.dm_set_dev_dir(c_dir) + if res != 1 { + return fmt.Errorf("dm_set_dev_dir failed") + } + return nil +} + +func GetLibraryVersion() (string, error) { + buffer := (*C.char)(C.malloc(128)) + defer C.free(unsafe.Pointer(buffer)) + res := C.dm_get_library_version(buffer, 128) + if res != 1 { + return "", fmt.Errorf("dm_get_library_version failed") + } else { + return C.GoString(buffer), nil + } +} + +type TaskType int + +const ( + DeviceCreate TaskType = iota + DeviceReload + DeviceRemove + DeviceRemoveAll + DeviceSuspend + DeviceResume + DeviceInfo + DeviceDeps + DeviceRename + DeviceVersion + DeviceStatus + DeviceTable + DeviceWaitevent + DeviceList + DeviceClear + DeviceMknodes + DeviceListVersions + DeviceTargetMsg + DeviceSetGeometry +) + +type Task struct { + unmanaged *C.struct_dm_task +} + +type Info struct { + Exists int + Suspended int + LiveTable int + InactiveTable int + OpenCount int32 + EventNr uint32 + Major uint32 + Minor uint32 + ReadOnly int + TargetCount int32 +} + +func (t *Task) destroy() { + if t != nil { + C.dm_task_destroy(t.unmanaged) + runtime.SetFinalizer(t, nil) + } +} + +func TaskCreate(tasktype TaskType) *Task { + c_task := C.dm_task_create(C.int(int(tasktype))) + if c_task == nil { + return nil + } + task := &Task{c_task} + runtime.SetFinalizer(task, (*Task).destroy) + return task +} + +func (t *Task) Run() error { + res := C.dm_task_run(t.unmanaged) + if res != 1 { + return fmt.Errorf("dm_task_run failed") + } + return nil +} + +func (t *Task) SetName(name string) error { + c_name := C.CString(name) + defer C.free(unsafe.Pointer(c_name)) + + res := C.dm_task_set_name(t.unmanaged, c_name) + if res != 1 { + return fmt.Errorf("dm_task_set_name failed") + } + return nil +} + +func (t *Task) SetMessage(message string) error { + c_message := C.CString(message) + defer C.free(unsafe.Pointer(c_message)) + + res := C.dm_task_set_message(t.unmanaged, c_message) + if res != 1 { + return fmt.Errorf("dm_task_set_message failed") + } + return nil +} + +func (t *Task) SetSector(sector uint64) error { + res := C.dm_task_set_sector(t.unmanaged, C.uint64_t(sector)) + if res != 1 { + return fmt.Errorf("dm_task_set_add_node failed") + } + return nil +} + +func (t *Task) SetCookie(cookie *uint32, flags uint16) error { + var c_cookie C.uint32_t + c_cookie = C.uint32_t(*cookie) + res := C.dm_task_set_cookie(t.unmanaged, &c_cookie, C.uint16_t(flags)) + if res != 1 { + return fmt.Errorf("dm_task_set_add_node failed") + } + *cookie = uint32(c_cookie) + return nil +} + +func (t *Task) SetRo() error { + res := C.dm_task_set_ro(t.unmanaged) + if res != 1 { + return fmt.Errorf("dm_task_set_ro failed") + } + return nil +} + +func (t *Task) AddTarget(start uint64, size uint64, ttype string, params string) error { + c_ttype := C.CString(ttype) + defer C.free(unsafe.Pointer(c_ttype)) + + c_params := C.CString(params) + defer C.free(unsafe.Pointer(c_params)) + + res := C.dm_task_add_target(t.unmanaged, C.uint64_t(start), C.uint64_t(size), c_ttype, c_params) + if res != 1 { + return fmt.Errorf("dm_task_add_target failed") + } + return nil +} + +func (t *Task) GetDriverVersion() (string, error) { + buffer := (*C.char)(C.malloc(128)) + defer C.free(unsafe.Pointer(buffer)) + + res := C.dm_task_get_driver_version(t.unmanaged, buffer, 128) + if res != 1 { + return "", fmt.Errorf("dm_task_get_driver_version") + } else { + return C.GoString(buffer), nil + } +} + +func (t *Task) GetInfo() (*Info, error) { + c_info := C.struct_dm_info{} + res := C.dm_task_get_info(t.unmanaged, &c_info) + if res != 1 { + return nil, fmt.Errorf("dm_task_get_driver_version") + } else { + info := &Info{} + info.Exists = int(c_info.exists) + info.Suspended = int(c_info.suspended) + info.LiveTable = int(c_info.live_table) + info.InactiveTable = int(c_info.inactive_table) + info.OpenCount = int32(c_info.open_count) + info.EventNr = uint32(c_info.event_nr) + info.Major = uint32(c_info.major) + info.Minor = uint32(c_info.minor) + info.ReadOnly = int(c_info.read_only) + info.TargetCount = int32(c_info.target_count) + + return info, nil + } +} + +func (t *Task) GetNextTarget(next uintptr) (uintptr, uint64, uint64, string, string) { + nextp := unsafe.Pointer(next) + var c_start C.uint64_t + var c_length C.uint64_t + var c_target_type *C.char + var c_params *C.char + + nextp = C.dm_get_next_target(t.unmanaged, nextp, &c_start, &c_length, &c_target_type, &c_params) + + target_type := C.GoString(c_target_type) + params := C.GoString(c_params) + + return uintptr(nextp), uint64(c_start), uint64(c_length), target_type, params +} + +func AttachLoopDevice(filename string) (*os.File, error) { + c_filename := C.CString(filename) + defer C.free(unsafe.Pointer(c_filename)) + + var fd C.int + res := C.attach_loop_device(c_filename, &fd) + if res == nil { + return nil, fmt.Errorf("error loopback mounting") + } + file := os.NewFile(uintptr(fd), C.GoString(res)) + C.free(unsafe.Pointer(res)) + return file, nil +} + +func GetBlockDeviceSize(file *os.File) (uint64, error) { + fd := file.Fd() + size := C.get_block_size(C.int(fd)) + if size == -1 { + return 0, fmt.Errorf("Can't get block size") + } + return uint64(size), nil + +} + +func UdevWait(cookie uint32) error { + res := C.dm_udev_wait(C.uint32_t(cookie)) + if res != 1 { + return fmt.Errorf("Failed to wait on udev cookie %d", cookie) + } + return nil +} + +func LogInitVerbose(level int) { + C.dm_log_init_verbose(C.int(level)) +} From 374a5e9913112c5bde590e532bc0ba5e4afeda49 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:18:23 +0200 Subject: [PATCH 060/126] devmapper: Add DeviceSet device-mapper helper This is a module that uses the device-mapper create CoW snapshots You instantiate a DeviceSetDM object on a specified root (/var/lib/docker), and it will create a subdirectory there called "loopback". It will contain two sparse files which are loopback mounted into a thin-pool device-mapper device called "docker-pool". We then create a base snapshot in the pool with an empty filesystem which can be used as a base for docker snapshots. It also keeps track of the mapping between docker image ids and the snapshots in the pool. Typical use of is something like (without error checking): devices = NewDeviceSetDM("/var/lib/docker") devices.AddDevice(imageId, "") // "" is the base image id devices.MountDevice(imageId, "/mnt/image") ... extract base image to /mnt/image devices.AddDevice(containerId, imageId) devices.MountDevice(containerId, "/mnt/container") ... start container at /mnt/container --- devmapper/deviceset_devmapper.go | 895 +++++++++++++++++++++++++++++++ 1 file changed, 895 insertions(+) create mode 100644 devmapper/deviceset_devmapper.go diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go new file mode 100644 index 0000000000..d7e122cf28 --- /dev/null +++ b/devmapper/deviceset_devmapper.go @@ -0,0 +1,895 @@ +package devmapper + +import ( + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "os/exec" + "path" + "path/filepath" + "syscall" +) + +const defaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024 +const defaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 +const 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"` +} + +type MetaData struct { + Devices map[string]*DevInfo `json:devices` +} + +type DeviceSetDM struct { + initialized bool + root string + MetaData + TransactionId uint64 + NewTransactionId uint64 + nextFreeDevice int +} + +func getDevName(name string) string { + return "/dev/mapper/" + name +} + +func (info *DevInfo) Name() string { + hash := info.Hash + if hash == "" { + hash = "base" + } + return fmt.Sprintf("docker-%s", hash) +} + +func (info *DevInfo) DevName() string { + return getDevName(info.Name()) +} + +func (devices *DeviceSetDM) loopbackDir() string { + return path.Join(devices.root, "loopback") +} + +func (devices *DeviceSetDM) jsonFile() string { + return path.Join(devices.loopbackDir(), "json") +} + +func (devices *DeviceSetDM) getPoolName() string { + return "docker-pool" +} + +func (devices *DeviceSetDM) getPoolDevName() string { + return getDevName(devices.getPoolName()) +} + +func (devices *DeviceSetDM) 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)) + } + err := task.SetName(name) + if err != nil { + return nil, fmt.Errorf("Can't set task name %s", name) + } + return task, nil +} + +func (devices *DeviceSetDM) getInfo(name string) (*Info, error) { + task, err := devices.createTask(DeviceInfo, name) + if task == nil { + return nil, err + } + err = task.Run() + if err != nil { + return nil, err + } + info, err := task.GetInfo() + if err != nil { + return nil, err + } + return info, nil +} + +func (devices *DeviceSetDM) getStatus(name string) (uint64, uint64, string, string, error) { + task, err := devices.createTask(DeviceStatus, name) + if task == nil { + return 0, 0, "", "", err + } + err = task.Run() + if err != nil { + return 0, 0, "", "", err + } + + devinfo, err := task.GetInfo() + if err != nil { + return 0, 0, "", "", err + } + if devinfo.Exists == 0 { + return 0, 0, "", "", fmt.Errorf("Non existing device %s", name) + } + + var next uintptr = 0 + next, start, length, target_type, params := task.GetNextTarget(next) + + return start, length, target_type, params, nil +} + +func (devices *DeviceSetDM) setTransactionId(oldId uint64, newId uint64) error { + task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) + if task == nil { + return err + } + + err = task.SetSector(0) + if err != nil { + return fmt.Errorf("Can't set sector") + } + + message := fmt.Sprintf("set_transaction_id %d %d", oldId, newId) + err = task.SetMessage(message) + if err != nil { + return fmt.Errorf("Can't set message") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running setTransactionId") + } + return nil +} + +func (devices *DeviceSetDM) hasImage(name string) bool { + dirname := devices.loopbackDir() + filename := path.Join(dirname, name) + + _, err := os.Stat(filename) + return err == nil +} + +func (devices *DeviceSetDM) ensureImage(name string, size int64) (string, error) { + dirname := devices.loopbackDir() + filename := path.Join(dirname, name) + + if err := os.MkdirAll(dirname, 0700); err != nil && !os.IsExist(err) { + return "", err + } + + _, err := os.Stat(filename) + if err != nil { + if !os.IsNotExist(err) { + return "", err + } + log.Printf("Creating loopback file %s for device-manage use", filename) + file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + return "", err + } + err = file.Truncate(size) + if err != nil { + return "", err + } + } + return filename, nil +} + +func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) error { + log.Printf("Activating device-mapper pool %s", devices.getPoolName()) + task, err := devices.createTask(DeviceCreate, devices.getPoolName()) + 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() + " 512 8192" + err = task.AddTarget(0, size/512, "thin-pool", params) + if err != nil { + return fmt.Errorf("Can't add target") + } + + var cookie uint32 = 0 + err = task.SetCookie(&cookie, 32) + if err != nil { + return fmt.Errorf("Can't set cookie") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running DeviceCreate") + } + + UdevWait(cookie) + + return nil +} + +func (devices *DeviceSetDM) suspendDevice(info *DevInfo) error { + task, err := devices.createTask(DeviceSuspend, info.Name()) + if task == nil { + return err + } + err = task.Run() + if err != nil { + return fmt.Errorf("Error running DeviceSuspend") + } + return nil +} + +func (devices *DeviceSetDM) resumeDevice(info *DevInfo) error { + task, err := devices.createTask(DeviceResume, info.Name()) + if task == nil { + return err + } + + var cookie uint32 = 0 + err = task.SetCookie(&cookie, 32) + if err != nil { + return fmt.Errorf("Can't set cookie") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running DeviceSuspend") + } + + UdevWait(cookie) + + return nil +} + +func (devices *DeviceSetDM) createDevice(deviceId int) error { + task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) + if task == nil { + return err + } + + err = task.SetSector(0) + if err != nil { + return fmt.Errorf("Can't set sector") + } + + message := fmt.Sprintf("create_thin %d", deviceId) + err = task.SetMessage(message) + if err != nil { + return fmt.Errorf("Can't set message") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running createDevice") + } + return nil +} + +func (devices *DeviceSetDM) createSnapDevice(deviceId int, baseInfo *DevInfo) error { + doSuspend := false + devinfo, _ := devices.getInfo(baseInfo.Name()) + if devinfo != nil && devinfo.Exists != 0 { + doSuspend = true + } + + if doSuspend { + err := devices.suspendDevice(baseInfo) + if err != nil { + return err + } + } + + task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) + if task == nil { + _ = devices.resumeDevice(baseInfo) + return err + } + err = task.SetSector(0) + if err != nil { + _ = devices.resumeDevice(baseInfo) + return fmt.Errorf("Can't set sector") + } + + message := fmt.Sprintf("create_snap %d %d", deviceId, baseInfo.DeviceId) + err = task.SetMessage(message) + if err != nil { + _ = devices.resumeDevice(baseInfo) + return fmt.Errorf("Can't set message") + } + + err = task.Run() + if err != nil { + _ = devices.resumeDevice(baseInfo) + return fmt.Errorf("Error running DeviceCreate") + } + + if doSuspend { + err = devices.resumeDevice(baseInfo) + if err != nil { + return err + } + } + + return nil +} + +func (devices *DeviceSetDM) deleteDevice(deviceId int) error { + task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) + if task == nil { + return err + } + + err = task.SetSector(0) + if err != nil { + return fmt.Errorf("Can't set sector") + } + + message := fmt.Sprintf("delete %d", deviceId) + err = task.SetMessage(message) + if err != nil { + return fmt.Errorf("Can't set message") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running deleteDevice") + } + return nil +} + +func (devices *DeviceSetDM) removeDevice(info *DevInfo) error { + task, err := devices.createTask(DeviceRemove, info.Name()) + if task == nil { + return err + } + err = task.Run() + if err != nil { + return fmt.Errorf("Error running removeDevice") + } + return nil +} + +func (devices *DeviceSetDM) activateDevice(info *DevInfo) error { + task, err := devices.createTask(DeviceCreate, info.Name()) + if task == nil { + return err + } + + params := fmt.Sprintf("%s %d", devices.getPoolDevName(), info.DeviceId) + err = task.AddTarget(0, info.Size/512, "thin", params) + if err != nil { + return fmt.Errorf("Can't add target") + } + + var cookie uint32 = 0 + err = task.SetCookie(&cookie, 32) + if err != nil { + return fmt.Errorf("Can't set cookie") + } + + err = task.Run() + if err != nil { + return fmt.Errorf("Error running DeviceCreate") + } + + UdevWait(cookie) + + return nil +} + +func (devices *DeviceSetDM) allocateDeviceId() int { + // TODO: Add smarter reuse of deleted devices + id := devices.nextFreeDevice + devices.nextFreeDevice = devices.nextFreeDevice + 1 + return id +} + +func (devices *DeviceSetDM) allocateTransactionId() uint64 { + devices.NewTransactionId = devices.NewTransactionId + 1 + return devices.NewTransactionId +} + +func (devices *DeviceSetDM) saveMetadata() error { + jsonData, err := json.Marshal(devices.MetaData) + if err != nil { + return err + } + tmpFile, err := ioutil.TempFile(filepath.Dir(devices.jsonFile()), ".json") + if err != nil { + return err + } + + n, err := tmpFile.Write(jsonData) + if err != nil { + return err + } + if n < len(jsonData) { + err = io.ErrShortWrite + } + err = tmpFile.Sync() + if err != nil { + return err + } + err = tmpFile.Close() + if err != nil { + return err + } + err = os.Rename(tmpFile.Name(), devices.jsonFile()) + if err != nil { + return err + } + + if devices.NewTransactionId != devices.TransactionId { + err = devices.setTransactionId(devices.TransactionId, devices.NewTransactionId) + if err != nil { + return err + } + devices.TransactionId = devices.NewTransactionId + } + + return nil +} + +func (devices *DeviceSetDM) registerDevice(id int, hash string, size uint64) (*DevInfo, error) { + transaction := devices.allocateTransactionId() + + info := &DevInfo{ + Hash: hash, + DeviceId: id, + Size: size, + TransactionId: transaction, + Initialized: false, + } + + devices.Devices[hash] = info + err := devices.saveMetadata() + if err != nil { + // Try to remove unused device + devices.Devices[hash] = nil + return nil, err + } + + return info, nil +} + +func (devices *DeviceSetDM) activateDeviceIfNeeded(hash string) error { + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("Unknown device %s", hash) + } + + name := info.Name() + devinfo, _ := devices.getInfo(name) + if devinfo != nil && devinfo.Exists != 0 { + return nil + } + + return devices.activateDevice(info) +} + +func (devices *DeviceSetDM) createFilesystem(info *DevInfo) error { + devname := info.DevName() + + err := exec.Command("mkfs.ext4", "-E", + "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run() + if err != nil { + err = exec.Command("mkfs.ext4", "-E", + "discard,lazy_itable_init=0", devname).Run() + } + if err != nil { + return err + } + return nil +} + +func (devices *DeviceSetDM) loadMetaData() error { + _, _, _, params, err := devices.getStatus(devices.getPoolName()) + if err != nil { + return err + } + var currentTransaction uint64 + _, err = fmt.Sscanf(params, "%d", ¤tTransaction) + if err != nil { + return err + } + + devices.TransactionId = currentTransaction + devices.NewTransactionId = devices.TransactionId + + jsonData, err := ioutil.ReadFile(devices.jsonFile()) + if err != nil && !os.IsNotExist(err) { + return err + } + + metadata := &MetaData{ + Devices: make(map[string]*DevInfo), + } + if jsonData != nil { + if err := json.Unmarshal(jsonData, metadata); err != nil { + return err + } + } + devices.MetaData = *metadata + + for hash, d := range devices.Devices { + d.Hash = hash + + 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 > currentTransaction { + log.Printf("Removing lost device %s with id %d", hash, d.TransactionId) + delete(devices.Devices, hash) + } + } + + return nil +} + +func (devices *DeviceSetDM) createBaseLayer(dir string) error { + for pth, typ := range map[string]string{ + "/dev/pts": "dir", + "/dev/shm": "dir", + "/proc": "dir", + "/sys": "dir", + "/.dockerinit": "file", + "/etc/resolv.conf": "file", + "/etc/hosts": "file", + "/etc/hostname": "file", + // "var/run": "dir", + // "var/lock": "dir", + } { + if _, err := os.Stat(path.Join(dir, pth)); err != nil { + if os.IsNotExist(err) { + switch typ { + case "dir": + if err := os.MkdirAll(path.Join(dir, pth), 0755); err != nil { + return err + } + case "file": + if err := os.MkdirAll(path.Join(dir, path.Dir(pth)), 0755); err != nil { + return err + } + + if f, err := os.OpenFile(path.Join(dir, pth), os.O_CREATE, 0755); err != nil { + return err + } else { + f.Close() + } + } + } else { + return err + } + } + } + return nil +} + +func (devices *DeviceSetDM) setupBaseImage() error { + oldInfo := devices.Devices[""] + if oldInfo != nil && oldInfo.Initialized { + return nil + } + + if oldInfo != nil && !oldInfo.Initialized { + log.Printf("Removing uninitialized base image") + if err := devices.RemoveDevice(""); err != nil { + return err + } + } + + log.Printf("Initializing base device-manager snapshot") + + id := devices.allocateDeviceId() + + // Create initial device + err := devices.createDevice(id) + if err != nil { + return err + } + + info, err := devices.registerDevice(id, "", defaultBaseFsSize) + if err != nil { + _ = devices.deleteDevice(id) + return err + } + + log.Printf("Creating filesystem on base device-manager snapshot") + + err = devices.activateDeviceIfNeeded("") + if err != nil { + return err + } + + err = devices.createFilesystem(info) + if err != nil { + return err + } + + tmpDir := path.Join(devices.loopbackDir(), "basefs") + if err = os.MkdirAll(tmpDir, 0700); err != nil && !os.IsExist(err) { + return err + } + + err = devices.MountDevice("", tmpDir) + if err != nil { + return err + } + + err = devices.createBaseLayer(tmpDir) + if err != nil { + _ = syscall.Unmount(tmpDir, 0) + return err + } + + err = syscall.Unmount(tmpDir, 0) + if err != nil { + return err + } + + _ = os.Remove(tmpDir) + + info.Initialized = true + + err = devices.saveMetadata() + if err != nil { + info.Initialized = false + return err + } + + return nil +} + +func (devices *DeviceSetDM) initDevmapper() error { + info, err := devices.getInfo(devices.getPoolName()) + if info == nil { + return err + } + + if info.Exists != 0 { + /* Pool exists, assume everything is up */ + err = devices.loadMetaData() + if err != nil { + return err + } + err = devices.setupBaseImage() + if err != nil { + return err + } + return nil + } + + createdLoopback := false + if !devices.hasImage("data") || !devices.hasImage("metadata") { + /* If we create the loopback mounts we also need to initialize the base fs */ + createdLoopback = true + } + + data, err := devices.ensureImage("data", defaultDataLoopbackSize) + if err != nil { + return err + } + + metadata, err := devices.ensureImage("metadata", defaultMetaDataLoopbackSize) + if err != nil { + return err + } + + dataFile, err := AttachLoopDevice(data) + if err != nil { + return err + } + defer dataFile.Close() + + metadataFile, err := AttachLoopDevice(metadata) + if err != nil { + return err + } + defer metadataFile.Close() + + err = devices.createPool(dataFile, metadataFile) + if err != nil { + return err + } + + if !createdLoopback { + err = devices.loadMetaData() + if err != nil { + return err + } + } + + err = devices.setupBaseImage() + if err != nil { + return err + } + + return nil +} + +func (devices *DeviceSetDM) AddDevice(hash, baseHash string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + if devices.Devices[hash] != nil { + return fmt.Errorf("hash %s already exists", hash) + } + + baseInfo := devices.Devices[baseHash] + if baseInfo == nil { + return fmt.Errorf("Unknown base hash %s", baseHash) + } + + deviceId := devices.allocateDeviceId() + + err := devices.createSnapDevice(deviceId, baseInfo) + if err != nil { + return err + } + + _, err = devices.registerDevice(deviceId, hash, baseInfo.Size) + if err != nil { + _ = devices.deleteDevice(deviceId) + return err + } + return nil +} + +func (devices *DeviceSetDM) RemoveDevice(hash string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("hash %s doesn't exists", hash) + } + + devinfo, _ := devices.getInfo(info.Name()) + if devinfo != nil && devinfo.Exists != 0 { + err := devices.removeDevice(info) + if err != nil { + return err + } + } + + if info.Initialized { + info.Initialized = false + err := devices.saveMetadata() + if err != nil { + return err + } + } + + err := devices.deleteDevice(info.DeviceId) + if err != nil { + return err + } + + _ = devices.allocateTransactionId() + delete(devices.Devices, info.Hash) + + err = devices.saveMetadata() + if err != nil { + devices.Devices[info.Hash] = info + return err + } + + return nil +} + +func (devices *DeviceSetDM) DeactivateDevice(hash string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("hash %s doesn't exists", hash) + } + + devinfo, err := devices.getInfo(info.Name()) + if err != nil { + return err + } + if devinfo.Exists != 0 { + err := devices.removeDevice(info) + if err != nil { + return err + } + } + + return nil +} + +func (devices *DeviceSetDM) MountDevice(hash, path string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + err := devices.activateDeviceIfNeeded(hash) + if err != nil { + return err + } + + info := devices.Devices[hash] + + err = syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "discard") + if err != nil && err == syscall.EINVAL { + err = syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "") + } + if err != nil { + return err + } + return nil +} + +func (devices *DeviceSetDM) HasDevice(hash string) bool { + if err := devices.ensureInit(); err != nil { + return false + } + + info := devices.Devices[hash] + return info != nil +} + +func (devices *DeviceSetDM) HasInitializedDevice(hash string) bool { + if err := devices.ensureInit(); err != nil { + return false + } + + info := devices.Devices[hash] + return info != nil && info.Initialized +} + +func (devices *DeviceSetDM) SetInitialized(hash string) error { + if err := devices.ensureInit(); err != nil { + return err + } + + info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("Unknown device %s", hash) + } + + info.Initialized = true + err := devices.saveMetadata() + if err != nil { + info.Initialized = false + return err + } + + return nil +} + +func (devices *DeviceSetDM) ensureInit() error { + if (!devices.initialized) { + devices.initialized = true + err := devices.initDevmapper() + if err != nil { + return err + } + } + return nil +} + +func NewDeviceSetDM(root string) *DeviceSetDM { + SetDevDir("/dev") + devices := &DeviceSetDM{ + initialized: false, + root: root, + } + devices.Devices = make(map[string]*DevInfo) + + return devices +} From 7fb3bfed03d4d2a88f0e76fd6b8425cc753f4547 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:25:32 +0200 Subject: [PATCH 061/126] devmapper: Add simple tool to test the DeviceSet commands --- devmapper/docker-device-tool/device_tool.go | 62 +++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 devmapper/docker-device-tool/device_tool.go diff --git a/devmapper/docker-device-tool/device_tool.go b/devmapper/docker-device-tool/device_tool.go new file mode 100644 index 0000000000..28bdf56074 --- /dev/null +++ b/devmapper/docker-device-tool/device_tool.go @@ -0,0 +1,62 @@ +package main + +import ( + "fmt" + "github.com/dotcloud/docker/devmapper" + "os" +) + +func usage() { + fmt.Printf("Usage: %s [snap new-id base-id] | [remove id] | [mount id mountpoint]\n", os.Args[0]) + os.Exit(1) +} + +func main() { + devices := devmapper.NewDeviceSetDM("/var/lib/docker") + + if len(os.Args) < 2 { + usage() + } + + cmd := os.Args[1] + if cmd == "snap" { + if len(os.Args) < 4 { + usage() + } + + err := devices.AddDevice(os.Args[2], os.Args[3]) + if err != nil { + fmt.Println("Can't create snap device: ", err) + os.Exit(1) + } + } else if cmd == "remove" { + if len(os.Args) < 3 { + usage() + } + + err := devices.RemoveDevice(os.Args[2]) + if err != nil { + fmt.Println("Can't remove device: ", err) + os.Exit(1) + } + } else if cmd == "mount" { + if len(os.Args) < 4 { + usage() + } + + err := devices.MountDevice(os.Args[2], os.Args[3]) + if err != nil { + fmt.Println("Can't create snap device: ", err) + os.Exit(1) + } + } else { + fmt.Printf("Unknown command %s\n", cmd) + if len(os.Args) < 4 { + usage() + } + + os.Exit(1) + } + + return +} From b8dc7b5f1a8111275e2b869dfb67a8689cecf9b9 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 10 Sep 2013 20:15:23 +0200 Subject: [PATCH 062/126] Add a separate docker-init binary This may be used for the .dockerinit case if the main binary is not statically linked. --- docker-init/docker-init.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 docker-init/docker-init.go diff --git a/docker-init/docker-init.go b/docker-init/docker-init.go new file mode 100644 index 0000000000..c368c53138 --- /dev/null +++ b/docker-init/docker-init.go @@ -0,0 +1,16 @@ +package main + +import ( + "github.com/dotcloud/docker" +) + +var ( + GITCOMMIT string + VERSION string +) + +func main() { + // Running in init mode + docker.SysInit() + return +} From 167601e85850aa58df96f9d0796f9c26ed2d6fa4 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 10 Sep 2013 20:21:15 +0200 Subject: [PATCH 063/126] Runtime: Automatically use docker-init if it exists In some builds the main docker binary is not statically linked, and as such not usable in as the .dockerinit binary, for those cases we look for a separately shipped docker-init binary and use that instead. --- runtime.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/runtime.go b/runtime.go index d77ecca722..ad4672b040 100644 --- a/runtime.go +++ b/runtime.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path" + "path/filepath" "sort" "strings" "time" @@ -42,7 +43,17 @@ type Runtime struct { var sysInitPath string func init() { - sysInitPath = utils.SelfPath() + selfPath := utils.SelfPath() + + // If we have a separate docker-init, use that, otherwise use the + // main docker binary + dir := filepath.Dir(selfPath) + dockerInitPath := filepath.Join(dir, "docker-init") + if _, err := os.Stat(dockerInitPath); err != nil { + sysInitPath = selfPath + } else { + sysInitPath = dockerInitPath + } } // List returns an array of all containers registered in the runtime. From e368c8bb01b3c52c8e4c334c3a7f32556af9d632 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:41:38 +0200 Subject: [PATCH 064/126] Image: Add runtime and container id args to Mount() We will later need the runtime to get access to the VolumeSet singleton, and the container id to have a name for the volume for the container --- container.go | 2 +- graph_test.go | 5 ++++- image.go | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/container.go b/container.go index 316e0d259f..b74e9f2f0e 100644 --- a/container.go +++ b/container.go @@ -1158,7 +1158,7 @@ func (container *Container) Mount() error { if err != nil { return err } - return image.Mount(container.RootfsPath(), container.rwPath()) + return image.Mount(container.runtime, container.RootfsPath(), container.rwPath(), container.ID) } func (container *Container) Changes() ([]Change, error) { diff --git a/graph_test.go b/graph_test.go index 471016938d..d89e6efe9d 100644 --- a/graph_test.go +++ b/graph_test.go @@ -121,6 +121,9 @@ func TestRegister(t *testing.T) { } func TestMount(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + graph := tempGraph(t) defer os.RemoveAll(graph.Root) archive, err := fakeTar() @@ -144,7 +147,7 @@ func TestMount(t *testing.T) { if err := os.MkdirAll(rw, 0700); err != nil { t.Fatal(err) } - if err := image.Mount(rootfs, rw); err != nil { + if err := image.Mount(runtime, rootfs, rw, "testing"); err != nil { t.Fatal(err) } // FIXME: test for mount contents diff --git a/image.go b/image.go index 9f34160b80..bbf559e23a 100644 --- a/image.go +++ b/image.go @@ -170,7 +170,7 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) { return Tar(layerPath, compression) } -func (image *Image) Mount(root, rw string) error { +func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { if mounted, err := Mounted(root); err != nil { return err } else if mounted { From e6216793d91a9b003814b535b0373902326753dd Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 11:44:11 +0200 Subject: [PATCH 065/126] Add DeviceSet interface This interface matches the device-mapper implementation (DeviceSetDM) but is free from any dependencies. This allows core docker code to refer to a DeviceSet without having an explicit dependency on the devmapper package. This is important, because the devmapper package has external dependencies which are not wanted in the docker client app, as it needs to run with minimal dependencies in the docker image. --- deviceset.go | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 deviceset.go diff --git a/deviceset.go b/deviceset.go new file mode 100644 index 0000000000..01bdba411f --- /dev/null +++ b/deviceset.go @@ -0,0 +1,11 @@ +package docker + +type DeviceSet interface { + AddDevice(hash, baseHash string) error + SetInitialized(hash string) error + DeactivateDevice(hash string) error + RemoveDevice(hash string) error + MountDevice(hash, path string) error + HasDevice(hash string) bool + HasInitializedDevice(hash string) bool +} From 1d36b8c7b7b0c943ccb7d69b7181b3e33566d77c Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 12:03:45 +0200 Subject: [PATCH 066/126] Server: Pass in device-mapper DeviceSet to server This makes docker (but not docker-init) link to libdevmapper and will allow it to use the DeviceSet --- docker/docker.go | 3 ++- server.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docker/docker.go b/docker/docker.go index a0021f3a87..4dcc174d5e 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "github.com/dotcloud/docker" + "github.com/dotcloud/docker/devmapper" "github.com/dotcloud/docker/utils" "io/ioutil" "log" @@ -133,7 +134,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart if flDns != "" { dns = []string{flDns} } - server, err := docker.NewServer(flGraphPath, autoRestart, enableCors, dns) + server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns) if err != nil { return err } diff --git a/server.go b/server.go index 5bfbc96924..9db3ca7298 100644 --- a/server.go +++ b/server.go @@ -1299,7 +1299,7 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er } -func NewServer(flGraphPath string, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { +func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } From ca2f7f955e697091f2b7bee9a33c6c4e106cecd0 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 14:40:12 +0200 Subject: [PATCH 067/126] Runtime: Add DeviceSet singleton This adds a DeviceSet singleton to the Runtime object which will be used for any DeviceMapper dependent code. --- runtime.go | 15 ++++++++++++--- runtime_test.go | 5 +++-- server.go | 2 +- utils_test.go | 3 ++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/runtime.go b/runtime.go index ad4672b040..91be6c79d8 100644 --- a/runtime.go +++ b/runtime.go @@ -38,6 +38,7 @@ type Runtime struct { volumes *Graph srv *Server Dns []string + deviceSet DeviceSet } var sysInitPath string @@ -75,6 +76,13 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element { return nil } +func (runtime *Runtime) GetDeviceSet() (DeviceSet, error) { + if runtime.deviceSet == nil { + return nil, fmt.Errorf("No device set available") + } + return runtime.deviceSet, nil +} + // Get looks for a container by the specified ID or name, and returns it. // If the container is not found, or if an error occurs, nil is returned. func (runtime *Runtime) Get(name string) *Container { @@ -439,8 +447,8 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a } // FIXME: harmonize with NewGraph() -func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, error) { - runtime, err := NewRuntimeFromDirectory(flGraphPath, autoRestart) +func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string) (*Runtime, error) { + runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart) if err != nil { return nil, err } @@ -458,7 +466,7 @@ func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, e return runtime, nil } -func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { +func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) (*Runtime, error) { runtimeRepo := path.Join(root, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { @@ -495,6 +503,7 @@ func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { capabilities: &Capabilities{}, autoRestart: autoRestart, volumes: volumes, + deviceSet: deviceSet, } if err := runtime.restore(); err != nil { diff --git a/runtime_test.go b/runtime_test.go index f4f5d5af1e..503f519d12 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/devmapper" "io" "log" "net" @@ -87,7 +88,7 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, false); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreBase), false); err != nil { panic(err) } else { globalRuntime = runtime @@ -456,7 +457,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, false) + runtime2, err := NewRuntimeFromDirectory(runtime1.root, devmapper.NewDeviceSetDM(runtime1.root), false) if err != nil { t.Fatal(err) } diff --git a/server.go b/server.go index 9db3ca7298..f0f6f40257 100644 --- a/server.go +++ b/server.go @@ -1303,7 +1303,7 @@ func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } - runtime, err := NewRuntime(flGraphPath, autoRestart, dns) + runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns) if err != nil { return nil, err } diff --git a/utils_test.go b/utils_test.go index 740a5fc1bc..bc4dd65a3c 100644 --- a/utils_test.go +++ b/utils_test.go @@ -2,6 +2,7 @@ package docker import ( "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/devmapper" "io" "io/ioutil" "os" @@ -42,7 +43,7 @@ func newTestRuntime() (*Runtime, error) { return nil, err } - runtime, err := NewRuntimeFromDirectory(root, false) + runtime, err := NewRuntimeFromDirectory(root, devmapper.NewDeviceSetDM(root), false) if err != nil { return nil, err } From 8f7361279c13660a617886a523c50ecbc6849129 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 10 Sep 2013 22:15:18 +0200 Subject: [PATCH 068/126] Runtime: Add MountMethod to allow AUFS and device-mapper to coexist --- runtime.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/runtime.go b/runtime.go index 91be6c79d8..21769789f5 100644 --- a/runtime.go +++ b/runtime.go @@ -17,6 +17,13 @@ import ( ) var defaultDns = []string{"8.8.8.8", "8.8.4.4"} +type MountMethod int + +const ( + MountMethodNone MountMethod = iota + MountMethodAUFS + MountMethodDeviceMapper +) type Capabilities struct { MemoryLimit bool @@ -39,6 +46,7 @@ type Runtime struct { srv *Server Dns []string deviceSet DeviceSet + mountMethod MountMethod } var sysInitPath string @@ -76,6 +84,46 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element { return nil } +func hasFilesystemSupport(fstype string) bool { + content, err := ioutil.ReadFile("/proc/filesystems") + if err != nil { + log.Printf("WARNING: Unable to read /proc/filesystems, assuming fs %s is not supported.", fstype) + return false + } + lines := strings.Split(string(content), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "nodev") { + line = line[5:] + } + line = strings.TrimSpace(line) + if line == fstype { + return true + } + } + return false +} + +func (runtime *Runtime) GetMountMethod() MountMethod { + if runtime.mountMethod == MountMethodNone { + // Try to automatically pick a method + if hasFilesystemSupport("aufs") { + log.Printf("Using AUFS backend.") + runtime.mountMethod = MountMethodAUFS + } else { + _ = exec.Command("modprobe", "aufs").Run() + if hasFilesystemSupport("aufs") { + log.Printf("Using AUFS backend.") + runtime.mountMethod = MountMethodAUFS + } else { + log.Printf("Using device-mapper backend.") + runtime.mountMethod = MountMethodDeviceMapper + } + } + } + + return runtime.mountMethod +} + func (runtime *Runtime) GetDeviceSet() (DeviceSet, error) { if runtime.deviceSet == nil { return nil, fmt.Errorf("No device set available") From d2ba3e200576c2bcceb81d8d3d65b86fbf49313b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 12:55:48 +0200 Subject: [PATCH 069/126] Image: Initial support for device-mapper mounts This supports creating images from layers and mounting them for running a container. Not supported yet are: * Creating diffs between images/containers * Creating layers for new images from a device-mapper container --- image.go | 269 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 260 insertions(+), 9 deletions(-) diff --git a/image.go b/image.go index bbf559e23a..07eca11269 100644 --- a/image.go +++ b/image.go @@ -15,6 +15,7 @@ import ( "path/filepath" "strconv" "strings" + "syscall" "time" ) @@ -136,6 +137,10 @@ func jsonPath(root string) string { return path.Join(root, "json") } +func mountPath(root string) string { + return path.Join(root, "mount") +} + func MountAUFS(ro []string, rw string, target string) error { // FIXME: Now mount the layers rwBranch := fmt.Sprintf("%v=rw", rw) @@ -170,25 +175,271 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) { return Tar(layerPath, compression) } +func (image *Image) applyLayer(layer, target string) error { + oldmask := syscall.Umask(0) + defer syscall.Umask(oldmask) + err := filepath.Walk(layer, func(srcPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + // Skip root + if srcPath == layer { + return nil + } + + var srcStat syscall.Stat_t + err = syscall.Lstat(srcPath, &srcStat) + if err != nil { + return err + } + + relPath, err := filepath.Rel(layer, srcPath) + if err != nil { + return err + } + + targetPath := filepath.Join(target, relPath) + + // Skip AUFS metadata + if matched, err := filepath.Match(".wh..wh.*", relPath); err != nil || matched { + if err != nil || !f.IsDir() { + return err + } + return filepath.SkipDir + } + + // Find out what kind of modification happened + file := filepath.Base(srcPath) + + // If there is a whiteout, then the file was removed + if strings.HasPrefix(file, ".wh.") { + originalFile := file[len(".wh."):] + deletePath := filepath.Join(filepath.Dir(targetPath), originalFile) + + err = os.RemoveAll(deletePath) + if err != nil { + return err + } + } else { + var targetStat = &syscall.Stat_t{} + err := syscall.Lstat(targetPath, targetStat) + if err != nil { + if !os.IsNotExist(err) { + return err + } + targetStat = nil + } + + if targetStat != nil && !(targetStat.Mode&syscall.S_IFDIR == syscall.S_IFDIR && srcStat.Mode&syscall.S_IFDIR == syscall.S_IFDIR) { + // Unless both src and dest are directories we remove the target and recreate it + // This is a bit wasteful in the case of only a mode change, but that is unlikely + // to matter much + err = os.RemoveAll(targetPath) + if err != nil { + return err + } + targetStat = nil + } + + if f.IsDir() { + // Source is a directory + if targetStat == nil { + err = syscall.Mkdir(targetPath, srcStat.Mode&07777) + if err != nil { + return err + } + } else if srcStat.Mode&07777 != targetStat.Mode&07777 { + err = syscall.Chmod(targetPath, srcStat.Mode&07777) + if err != nil { + return err + } + } + } else if srcStat.Mode&syscall.S_IFLNK == syscall.S_IFLNK { + // Source is symlink + link, err := os.Readlink(srcPath) + if err != nil { + return err + } + + err = os.Symlink(link, targetPath) + if err != nil { + return err + } + } else if srcStat.Mode&syscall.S_IFBLK == syscall.S_IFBLK || + srcStat.Mode&syscall.S_IFCHR == syscall.S_IFCHR || + srcStat.Mode&syscall.S_IFIFO == syscall.S_IFIFO || + srcStat.Mode&syscall.S_IFSOCK == syscall.S_IFSOCK { + // Source is special file + err = syscall.Mknod(targetPath, srcStat.Mode, int(srcStat.Rdev)) + if err != nil { + return err + } + } else if srcStat.Mode&syscall.S_IFREG == syscall.S_IFREG { + // Source is regular file + fd, err := syscall.Open(targetPath, syscall.O_CREAT|syscall.O_WRONLY, srcStat.Mode&07777) + if err != nil { + return err + } + dstFile := os.NewFile(uintptr(fd), targetPath) + srcFile, err := os.Open(srcPath) + _, err = io.Copy(dstFile, srcFile) + if err != nil { + return err + } + _ = srcFile.Close() + _ = dstFile.Close() + } else { + return fmt.Errorf("Unknown type for file %s", srcPath) + } + + if srcStat.Mode&syscall.S_IFLNK != syscall.S_IFLNK { + err = syscall.Chown(targetPath, int(srcStat.Uid), int(srcStat.Gid)) + if err != nil { + return err + } + ts := []syscall.Timeval{ + syscall.NsecToTimeval(srcStat.Atim.Nano()), + syscall.NsecToTimeval(srcStat.Mtim.Nano()), + } + syscall.Utimes(targetPath, ts) + } + + } + return nil + }) + return err +} + +func (image *Image) ensureImageDevice(devices DeviceSet) error { + if devices.HasInitializedDevice(image.ID) { + return nil + } + + if image.Parent != "" && !devices.HasInitializedDevice(image.Parent) { + parentImg, err := image.GetParent() + if err != nil { + return fmt.Errorf("Error while getting parent image: %v", err) + } + err = parentImg.ensureImageDevice(devices) + if err != nil { + return err + } + } + + root, err := image.root() + if err != nil { + return err + } + + mountDir := mountPath(root) + if err := os.Mkdir(mountDir, 0600); err != nil && !os.IsExist(err) { + return err + } + + mounted, err := Mounted(mountDir) + if err == nil && mounted { + log.Printf("Image %s is unexpectedly mounted, unmounting...", image.ID) + err = syscall.Unmount(mountDir, 0) + if err != nil { + return err + } + } + + if devices.HasDevice(image.ID) { + log.Printf("Found non-initialized demove-mapper device for image %s, removing", image.ID) + err = devices.RemoveDevice(image.ID) + if err != nil { + return err + } + } + + log.Printf("Creating device-mapper device for image id %s", image.ID) + + err = devices.AddDevice(image.ID, image.Parent) + if err != nil { + return err + } + + utils.Debugf("Mounting device %s at %s for image setup", image.ID, mountDir) + err = devices.MountDevice(image.ID, mountDir) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + + utils.Debugf("Applying layer %s at %s", image.ID, mountDir) + err = image.applyLayer(layerPath(root), mountDir) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + + utils.Debugf("Unmounting %s", mountDir) + err = syscall.Unmount(mountDir, 0) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + + devices.SetInitialized(image.ID) + + // No need to the device-mapper device to hang around once we've written + // the image, it can be enabled on-demand when needed + devices.DeactivateDevice(image.ID) + + return nil +} + func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { if mounted, err := Mounted(root); err != nil { return err } else if mounted { return fmt.Errorf("%s is already mounted", root) } - layers, err := image.layers() - if err != nil { - return err - } // Create the target directories if they don't exist if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) { return err } - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return err - } - if err := MountAUFS(layers, rw, root); err != nil { - return err + switch runtime.GetMountMethod() { + case MountMethodNone: + return fmt.Errorf("No supported Mount implementation") + + case MountMethodAUFS: + if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { + return err + } + layers, err := image.layers() + if err != nil { + return err + } + if err := MountAUFS(layers, rw, root); err != nil { + return err + } + + case MountMethodDeviceMapper: + devices, err := runtime.GetDeviceSet() + if err != nil { + return err + } + err = image.ensureImageDevice(devices) + if err != nil { + return err + } + + if !devices.HasDevice(id) { + utils.Debugf("Creating device %s for container based on image %s", id, image.ID) + err = devices.AddDevice(id, image.ID) + if err != nil { + return err + } + } + + utils.Debugf("Mounting container %s at %s for container", id, root) + err = devices.MountDevice(id, root) + if err != nil { + return err + } } return nil } From a9ec1dbc9bec91e1c0f1b751a06680570a04e915 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 14:21:53 +0200 Subject: [PATCH 070/126] Image: Deactivate image device when unmounting container There is no need to keep all the device-mapper devices active, we can just activate them on demand if needed. --- container.go | 7 ++++++- image.go | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/container.go b/container.go index b74e9f2f0e..5bc9153993 100644 --- a/container.go +++ b/container.go @@ -1181,7 +1181,12 @@ func (container *Container) Mounted() (bool, error) { } func (container *Container) Unmount() error { - return Unmount(container.RootfsPath()) + image, err := container.GetImage() + if err != nil { + return err + } + err = image.Unmount(container.runtime, container.RootfsPath(), container.ID) + return err } // ShortID returns a shorthand version of the container's id for convenience. diff --git a/image.go b/image.go index 07eca11269..b306dfd956 100644 --- a/image.go +++ b/image.go @@ -444,6 +444,30 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { return nil } +func (image *Image) Unmount(runtime *Runtime, root string, id string) error { + switch runtime.GetMountMethod() { + case MountMethodNone: + return fmt.Errorf("No supported Unmount implementation") + + case MountMethodAUFS: + return Unmount(root) + + case MountMethodDeviceMapper: + err := syscall.Unmount(root, 0) + if err != nil { + return err + } + + // Try to deactivate the device as generally there is no use for it anymore + devices, err := runtime.GetDeviceSet() + if err != nil { + return err; + } + return devices.DeactivateDevice(id) + } + return nil +} + func (image *Image) Changes(rw string) ([]Change, error) { layers, err := image.layers() if err != nil { From 074f38d49377411cf0b805095c0d9909d4859f3c Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 4 Sep 2013 15:44:01 +0200 Subject: [PATCH 071/126] Image: Always create a .docker-id file in the devices we create Without this there is really no way to map back from the device-mapper devices to the actual docker image/container ids in case the json file somehow got lost --- image.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/image.go b/image.go index b306dfd956..9d9a64cceb 100644 --- a/image.go +++ b/image.go @@ -368,6 +368,13 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } + + err = ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + utils.Debugf("Applying layer %s at %s", image.ID, mountDir) err = image.applyLayer(layerPath(root), mountDir) if err != nil { @@ -427,12 +434,14 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { return err } + createdDevice := false if !devices.HasDevice(id) { utils.Debugf("Creating device %s for container based on image %s", id, image.ID) err = devices.AddDevice(id, image.ID) if err != nil { return err } + createdDevice = true } utils.Debugf("Mounting container %s at %s for container", id, root) @@ -440,6 +449,15 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { if err != nil { return err } + + if createdDevice { + err = ioutil.WriteFile(path.Join(root, ".docker-id"), []byte(id), 0600) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + } + } return nil } From 8e7cbbff504b1d5b0680d2a14821d1d7b0757ab0 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 15:32:57 +0200 Subject: [PATCH 072/126] devmapper: Base the device-mapper names on the root dir name This means the default is "docker-*", but for tests we get separate prefixes for each test. --- devmapper/deviceset_devmapper.go | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go index d7e122cf28..4544215b63 100644 --- a/devmapper/deviceset_devmapper.go +++ b/devmapper/deviceset_devmapper.go @@ -10,6 +10,7 @@ import ( "os/exec" "path" "path/filepath" + "strings" "syscall" ) @@ -18,11 +19,12 @@ const defaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 const 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"` + Hash string `json:"-"` + DeviceId int `json:"device_id"` + Size uint64 `json:"size"` + TransactionId uint64 `json:"transaction_id"` + Initialized bool `json:"initialized"` + devices *DeviceSetDM `json:"-"` } type MetaData struct { @@ -31,7 +33,8 @@ type MetaData struct { type DeviceSetDM struct { initialized bool - root string + root string + devicePrefix string MetaData TransactionId uint64 NewTransactionId uint64 @@ -47,7 +50,7 @@ func (info *DevInfo) Name() string { if hash == "" { hash = "base" } - return fmt.Sprintf("docker-%s", hash) + return fmt.Sprintf("%s-%s", info.devices.devicePrefix, hash) } func (info *DevInfo) DevName() string { @@ -63,7 +66,7 @@ func (devices *DeviceSetDM) jsonFile() string { } func (devices *DeviceSetDM) getPoolName() string { - return "docker-pool" + return fmt.Sprintf("%s-pool", devices.devicePrefix) } func (devices *DeviceSetDM) getPoolDevName() string { @@ -446,6 +449,7 @@ func (devices *DeviceSetDM) registerDevice(id int, hash string, size uint64) (*D Size: size, TransactionId: transaction, Initialized: false, + devices: devices, } devices.Devices[hash] = info @@ -520,6 +524,7 @@ func (devices *DeviceSetDM) loadMetaData() error { for hash, d := range devices.Devices { d.Hash = hash + d.devices = devices if d.DeviceId >= devices.nextFreeDevice { devices.nextFreeDevice = d.DeviceId + 1 @@ -873,7 +878,7 @@ func (devices *DeviceSetDM) SetInitialized(hash string) error { } func (devices *DeviceSetDM) ensureInit() error { - if (!devices.initialized) { + if !devices.initialized { devices.initialized = true err := devices.initDevmapper() if err != nil { @@ -885,9 +890,16 @@ func (devices *DeviceSetDM) ensureInit() error { func NewDeviceSetDM(root string) *DeviceSetDM { SetDevDir("/dev") + + base := filepath.Base(root) + if !strings.HasPrefix(base, "docker") { + base = "docker-" + base + } + devices := &DeviceSetDM{ initialized: false, - root: root, + root: root, + devicePrefix: base, } devices.Devices = make(map[string]*DevInfo) From 1c5dc26a7c0a0abb7bc59174768ec309f6c5fd4f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 18:03:49 +0200 Subject: [PATCH 073/126] Implement docker diff for device-mapper To do diffing we just compare file metadata, so this relies on things like size and mtime/ctime to catch any changes. Its *possible* to trick this by updating a file without changing the size and setting back the mtime/ctime, but that seems pretty unlikely to happen in reality, and lets us avoid comparing the actual file data. --- changes.go | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++- container.go | 6 ++- image.go | 39 ++++++++++++++++--- 3 files changed, 142 insertions(+), 7 deletions(-) diff --git a/changes.go b/changes.go index 43573cd606..00c9cc7c77 100644 --- a/changes.go +++ b/changes.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "strings" + "syscall" ) type ChangeType int @@ -33,7 +34,7 @@ func (change *Change) String() string { return fmt.Sprintf("%s %s", kind, change.Path) } -func Changes(layers []string, rw string) ([]Change, error) { +func ChangesAUFS(layers []string, rw string) ([]Change, error) { var changes []Change err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error { if err != nil { @@ -104,3 +105,104 @@ func Changes(layers []string, rw string) ([]Change, error) { } return changes, nil } + +func ChangesDirs(newDir, oldDir string) ([]Change, error) { + var changes []Change + err := filepath.Walk(newDir, func(newPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + var newStat syscall.Stat_t + err = syscall.Lstat(newPath, &newStat) + if err != nil { + return err + } + + // Rebase path + relPath, err := filepath.Rel(newDir, newPath) + if err != nil { + return err + } + relPath = filepath.Join("/", relPath) + + // Skip root + if relPath == "/" || relPath == "/.docker-id" { + return nil + } + + change := Change{ + Path: relPath, + } + + oldPath := filepath.Join(oldDir, relPath) + + var oldStat = &syscall.Stat_t{} + err = syscall.Lstat(oldPath, oldStat) + if err != nil { + if !os.IsNotExist(err) { + return err + } + oldStat = nil + } + + if oldStat == nil { + change.Kind = ChangeAdd + changes = append(changes, change) + } else { + if oldStat.Ino != newStat.Ino || + oldStat.Mode != newStat.Mode || + oldStat.Uid != newStat.Uid || + oldStat.Gid != newStat.Gid || + oldStat.Rdev != newStat.Rdev || + oldStat.Size != newStat.Size || + oldStat.Blocks != newStat.Blocks || + oldStat.Mtim != newStat.Mtim || + oldStat.Ctim != newStat.Ctim { + change.Kind = ChangeModify + changes = append(changes, change) + } + } + + return nil + }) + if err != nil { + return nil, err + } + err = filepath.Walk(oldDir, func(oldPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + // Rebase path + relPath, err := filepath.Rel(oldDir, oldPath) + if err != nil { + return err + } + relPath = filepath.Join("/", relPath) + + // Skip root + if relPath == "/" { + return nil + } + + change := Change{ + Path: relPath, + } + + newPath := filepath.Join(newDir, relPath) + + var newStat = &syscall.Stat_t{} + err = syscall.Lstat(newPath, newStat) + if err != nil && os.IsNotExist(err) { + change.Kind = ChangeDelete + changes = append(changes, change) + } + + return nil + }) + if err != nil { + return nil, err + } + return changes, nil +} diff --git a/container.go b/container.go index 5bc9153993..33e0b9b48e 100644 --- a/container.go +++ b/container.go @@ -1162,11 +1162,15 @@ func (container *Container) Mount() error { } func (container *Container) Changes() ([]Change, error) { + if err := container.EnsureMounted(); err != nil { + return nil, err + } + image, err := container.GetImage() if err != nil { return nil, err } - return image.Changes(container.rwPath()) + return image.Changes(container.runtime, container.RootfsPath(), container.rwPath(), container.ID) } func (container *Container) GetImage() (*Image, error) { diff --git a/image.go b/image.go index 9d9a64cceb..119e07c70e 100644 --- a/image.go +++ b/image.go @@ -486,12 +486,41 @@ func (image *Image) Unmount(runtime *Runtime, root string, id string) error { return nil } -func (image *Image) Changes(rw string) ([]Change, error) { - layers, err := image.layers() - if err != nil { - return nil, err +func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, error) { + switch runtime.GetMountMethod() { + case MountMethodAUFS: + layers, err := image.layers() + if err != nil { + return nil, err + } + return ChangesAUFS(layers, rw) + + case MountMethodDeviceMapper: + devices, err := runtime.GetDeviceSet() + if err != nil { + return nil, err + } + + if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { + return nil, err + } + + // We re-use rw for the temporary mount of the base image as its + // not used by device-mapper otherwise + err = devices.MountDevice(image.ID, rw) + if err != nil { + return nil, err + } + + changes, err := ChangesDirs(root, rw) + _ = syscall.Unmount(rw, 0) + if err != nil { + return nil, err + } + return changes, nil } - return Changes(layers, rw) + + return nil, fmt.Errorf("No supported Changes implementation") } func (image *Image) ShortID() string { From b86f67126c86a07aac155f38aefd6c1ef538e24d Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 20:11:18 +0200 Subject: [PATCH 074/126] Archive: Fix up tar commandline arguments in TarFilter() There is no need to duplicate the compression flags for every element in the filter. --- archive.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/archive.go b/archive.go index bb019fb033..80b305a418 100644 --- a/archive.go +++ b/archive.go @@ -90,8 +90,9 @@ func TarFilter(path string, compression Compression, filter []string) (io.Reader if filter == nil { filter = []string{"."} } + args = append(args, "-c"+compression.Flag()) for _, f := range filter { - args = append(args, "-c"+compression.Flag(), f) + args = append(args, f) } return CmdStream(exec.Command(args[0], args[1:]...)) } From fda6ff9c2707efbd1c9d1f2bf151b9d1d082d0c6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 22:10:29 +0200 Subject: [PATCH 075/126] Make TarFilter more useful There are a few changes: * Callers can specify if they want recursive behaviour or not * All file listings to tar are sent on stdin, to handle long lists better * We can pass in a list of filenames which will be created as empty files in the tarball This is exactly what we want for the creation of layer tarballs given a container fs, a set of files to add and a set of whiteout files to create. --- archive.go | 82 ++++++++++++++++++++++++++++++++++++++++++++----- archive_test.go | 6 ++-- container.go | 2 +- 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/archive.go b/archive.go index 80b305a418..f3f7b8c59e 100644 --- a/archive.go +++ b/archive.go @@ -80,21 +80,73 @@ func (compression *Compression) Extension() string { // Tar creates an archive from the directory at `path`, and returns it as a // stream of bytes. func Tar(path string, compression Compression) (io.Reader, error) { - return TarFilter(path, compression, nil) + return TarFilter(path, compression, nil, true, nil) +} + +func escapeName(name string) string { + escaped := make([]byte,0) + for i, c := range []byte(name) { + if i == 0 && c == '/' { + continue + } + // all printable chars except "-" which is 0x2d + if (0x20 <= c && c <= 0x7E) && c != 0x2d { + escaped = append(escaped, c) + } else { + escaped = append(escaped, fmt.Sprintf("\\%03o", c)...) + } + } + return string(escaped) } // Tar creates an archive from the directory at `path`, only including files whose relative // paths are included in `filter`. If `filter` is nil, then all files are included. -func TarFilter(path string, compression Compression, filter []string) (io.Reader, error) { - args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path} +func TarFilter(path string, compression Compression, filter []string, recursive bool, createFiles []string) (io.Reader, error) { + args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-",} if filter == nil { filter = []string{"."} } args = append(args, "-c"+compression.Flag()) - for _, f := range filter { - args = append(args, f) + + if !recursive { + args = append(args, "--no-recursion") } - return CmdStream(exec.Command(args[0], args[1:]...)) + + files := "" + for _, f := range filter { + files = files + escapeName(f) + "\n" + } + + tmpDir := "" + + if createFiles != nil { + tmpDir, err := ioutil.TempDir("", "docker-tar") + if err != nil { + return nil, err + } + + files = files + "-C" + tmpDir + "\n" + for _, f := range createFiles { + path := filepath.Join(tmpDir, f) + err := os.MkdirAll(filepath.Dir(path), 0600) + if err != nil { + return nil, err + } + + if file, err := os.OpenFile(path, os.O_CREATE, 0600); err != nil { + return nil, err + } else { + file.Close() + } + files = files + escapeName(f) + "\n" + } + } + + return CmdStream(exec.Command(args[0], args[1:]...), &files, func () { + if tmpDir != "" { + _ = os.RemoveAll(tmpDir) + } + }) } // Untar reads a stream of bytes from `archive`, parses it as a tar archive, @@ -141,7 +193,7 @@ func Untar(archive io.Reader, path string) error { // TarUntar aborts and returns the error. func TarUntar(src string, filter []string, dst string) error { utils.Debugf("TarUntar(%s %s %s)", src, filter, dst) - archive, err := TarFilter(src, Uncompressed, filter) + archive, err := TarFilter(src, Uncompressed, filter, true, nil) if err != nil { return err } @@ -228,7 +280,18 @@ func CopyFileWithTar(src, dst string) error { // CmdStream executes a command, and returns its stdout as a stream. // If the command fails to run or doesn't complete successfully, an error // will be returned, including anything written on stderr. -func CmdStream(cmd *exec.Cmd) (io.Reader, error) { +func CmdStream(cmd *exec.Cmd, input *string, atEnd func()) (io.Reader, error) { + if input != nil { + stdin, err := cmd.StdinPipe() + if err != nil { + return nil, err + } + // Write stdin if any + go func() { + _, _ = stdin.Write([]byte(*input)) + stdin.Close() + }() + } stdout, err := cmd.StdoutPipe() if err != nil { return nil, err @@ -259,6 +322,9 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) { } else { pipeW.Close() } + if atEnd != nil { + atEnd() + } }() // Run the command and return the pipe if err := cmd.Start(); err != nil { diff --git a/archive_test.go b/archive_test.go index 9a0a8e1b9e..c86b4511c4 100644 --- a/archive_test.go +++ b/archive_test.go @@ -14,7 +14,7 @@ import ( func TestCmdStreamLargeStderr(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello") - out, err := CmdStream(cmd) + out, err := CmdStream(cmd, nil, nil) if err != nil { t.Fatalf("Failed to start command: %s", err) } @@ -35,7 +35,7 @@ func TestCmdStreamLargeStderr(t *testing.T) { func TestCmdStreamBad(t *testing.T) { badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1") - out, err := CmdStream(badCmd) + out, err := CmdStream(badCmd, nil, nil) if err != nil { t.Fatalf("Failed to start command: %s", err) } @@ -50,7 +50,7 @@ func TestCmdStreamBad(t *testing.T) { func TestCmdStreamGood(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0") - out, err := CmdStream(cmd) + out, err := CmdStream(cmd, nil, nil) if err != nil { t.Fatal(err) } diff --git a/container.go b/container.go index 33e0b9b48e..668a3dd8e9 100644 --- a/container.go +++ b/container.go @@ -1278,5 +1278,5 @@ func (container *Container) Copy(resource string) (Archive, error) { filter = []string{path.Base(basePath)} basePath = path.Dir(basePath) } - return TarFilter(basePath, Uncompressed, filter) + return TarFilter(basePath, Uncompressed, filter, true, nil) } From b0626f403b168b9020a802ec3bc4ad8c9bbc2486 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 5 Sep 2013 22:14:19 +0200 Subject: [PATCH 076/126] Implement container.ExportRW() on device-mapper --- container.go | 10 +++++++++- image.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/container.go b/container.go index 668a3dd8e9..cd143f7236 100644 --- a/container.go +++ b/container.go @@ -1111,7 +1111,15 @@ func (container *Container) Resize(h, w int) error { } func (container *Container) ExportRw() (Archive, error) { - return Tar(container.rwPath(), Uncompressed) + if err := container.EnsureMounted(); err != nil { + return nil, err + } + + image, err := container.GetImage() + if err != nil { + return nil, err + } + return image.ExportChanges(container.runtime, container.RootfsPath(), container.rwPath(), container.ID) } func (container *Container) RwChecksum() (string, error) { diff --git a/image.go b/image.go index 119e07c70e..546c54a577 100644 --- a/image.go +++ b/image.go @@ -523,6 +523,37 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er return nil, fmt.Errorf("No supported Changes implementation") } +func (image *Image) ExportChanges(runtime *Runtime, root, rw, id string) (Archive, error) { + switch runtime.GetMountMethod() { + case MountMethodAUFS: + return Tar(rw, Uncompressed) + + case MountMethodDeviceMapper: + changes, err := image.Changes(runtime, root, rw, id) + if err != nil { + return nil, err + } + + files := make([]string, 0) + deletions := make([]string, 0) + for _, change := range changes { + if change.Kind == ChangeModify || change.Kind == ChangeAdd { + files = append(files, change.Path) + } + if change.Kind == ChangeDelete { + base := filepath.Base(change.Path) + dir := filepath.Dir(change.Path) + deletions = append(deletions, filepath.Join(dir, ".wh."+base)) + } + } + + return TarFilter(root, Uncompressed, files, false, deletions) + } + + return nil, fmt.Errorf("No supported Changes implementation") +} + + func (image *Image) ShortID() string { return utils.TruncateID(image.ID) } From 30890c7763e775d38c82d3eac5c1650e149137ae Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 12:03:21 +0200 Subject: [PATCH 077/126] Runtime: Delete corresponding devices when deleting container --- runtime.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/runtime.go b/runtime.go index 21769789f5..220ca1af6c 100644 --- a/runtime.go +++ b/runtime.go @@ -283,6 +283,11 @@ func (runtime *Runtime) Destroy(container *Container) error { if err := os.RemoveAll(container.root); err != nil { return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err) } + if runtime.GetMountMethod() == MountMethodDeviceMapper { + if err := runtime.deviceSet.RemoveDevice(container.ID); err != nil { + return fmt.Errorf("Unable to remove device for %v: %v", container.ID, err) + } + } return nil } From 99393cf3cfd08de769f0c37f06b912fb3771a080 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 12:04:00 +0200 Subject: [PATCH 078/126] Delete corresponding Devices when deleting Images If an image is deleted and there is a corresponding device for that image we also delete the image. --- runtime.go | 13 +++++++++++++ runtime_test.go | 2 +- server.go | 4 ++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/runtime.go b/runtime.go index 220ca1af6c..7d1c10a279 100644 --- a/runtime.go +++ b/runtime.go @@ -291,6 +291,19 @@ func (runtime *Runtime) Destroy(container *Container) error { return nil } +func (runtime *Runtime) DeleteImage(id string) error { + err := runtime.graph.Delete(id) + if err != nil { + return err + } + if runtime.GetMountMethod() == MountMethodDeviceMapper && runtime.deviceSet.HasDevice(id) { + if err := runtime.deviceSet.RemoveDevice(id); err != nil { + return fmt.Errorf("Unable to remove device for %v: %v", id, err) + } + } + return nil +} + func (runtime *Runtime) restore() error { wheel := "-\\|/" if os.Getenv("DEBUG") == "" && os.Getenv("TEST") == "" { diff --git a/runtime_test.go b/runtime_test.go index 503f519d12..f9a209008c 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -57,7 +57,7 @@ func cleanup(runtime *Runtime) error { } for _, image := range images { if image.ID != unitTestImageID { - runtime.graph.Delete(image.ID) + runtime.DeleteImage(image.ID) } } return nil diff --git a/server.go b/server.go index f0f6f40257..f3081db81a 100644 --- a/server.go +++ b/server.go @@ -1025,7 +1025,7 @@ func (srv *Server) deleteImageAndChildren(id string, imgs *[]APIRmi) error { if err := srv.runtime.repositories.DeleteAll(id); err != nil { return err } - err := srv.runtime.graph.Delete(id) + err := srv.runtime.DeleteImage(id) if err != nil { return err } @@ -1099,7 +1099,7 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) { return nil, fmt.Errorf("No such image: %s", name) } if !autoPrune { - if err := srv.runtime.graph.Delete(img.ID); err != nil { + if err := srv.runtime.DeleteImage(img.ID); err != nil { return nil, fmt.Errorf("Error deleting image %s: %s", name, err) } return nil, nil From 381ce94ef4fe2590b0afa16c5f35a508de12e7a3 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 14:37:04 +0200 Subject: [PATCH 079/126] Add DeviceSetWrapper This wraps an existing DeviceSet and just adds a prefix to all ids in it. This will be useful for reusing a single DeviceSet for all the tests (but with separate ids) --- deviceset.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/deviceset.go b/deviceset.go index 01bdba411f..2caaf153b2 100644 --- a/deviceset.go +++ b/deviceset.go @@ -9,3 +9,52 @@ type DeviceSet interface { HasDevice(hash string) bool HasInitializedDevice(hash string) bool } + +type DeviceSetWrapper struct { + wrapped DeviceSet + prefix string +} + +func (wrapper *DeviceSetWrapper) wrap(hash string) string { + if hash != "" { + hash = wrapper.prefix + "-" + hash + } + return hash +} + + +func (wrapper *DeviceSetWrapper) AddDevice(hash, baseHash string) error { + return wrapper.wrapped.AddDevice(wrapper.wrap(hash), wrapper.wrap(baseHash)) +} + +func (wrapper *DeviceSetWrapper) SetInitialized(hash string) error { + return wrapper.wrapped.SetInitialized(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) DeactivateDevice(hash string) error { + return wrapper.wrapped.DeactivateDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) RemoveDevice(hash string) error { + return wrapper.wrapped.RemoveDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) MountDevice(hash, path string) error { + return wrapper.wrapped.MountDevice(wrapper.wrap(hash), path) +} + +func (wrapper *DeviceSetWrapper) HasDevice(hash string) bool { + return wrapper.wrapped.HasDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) HasInitializedDevice(hash string) bool { + return wrapper.wrapped.HasInitializedDevice(wrapper.wrap(hash)) +} + +func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { + wrapper := &DeviceSetWrapper{ + wrapped: wrapped, + prefix: prefix, + } + return wrapper +} From 52294192b2a008b624862701cbf8491ad19b0798 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 14:38:47 +0200 Subject: [PATCH 080/126] Reuse a single DeviceSetDM for all the tests We wrap the "real" DeviceSet for each test so that we get only a single device-mapper pool and loopback mounts, but still separate out the IDs in the tests. This makes the test run much faster. --- runtime_test.go | 2 +- utils_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime_test.go b/runtime_test.go index f9a209008c..5fa6c46bfe 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -457,7 +457,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, devmapper.NewDeviceSetDM(runtime1.root), false) + runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false) if err != nil { t.Fatal(err) } diff --git a/utils_test.go b/utils_test.go index bc4dd65a3c..f5bdac3271 100644 --- a/utils_test.go +++ b/utils_test.go @@ -2,7 +2,7 @@ package docker import ( "github.com/dotcloud/docker/utils" - "github.com/dotcloud/docker/devmapper" + "path/filepath" "io" "io/ioutil" "os" @@ -43,7 +43,7 @@ func newTestRuntime() (*Runtime, error) { return nil, err } - runtime, err := NewRuntimeFromDirectory(root, devmapper.NewDeviceSetDM(root), false) + runtime, err := NewRuntimeFromDirectory(root, NewDeviceSetWrapper (globalRuntime.deviceSet, filepath.Base(root)), false) if err != nil { return nil, err } From 6094257b28f2e4b5e1a6616c77961b5cec0c9195 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 6 Sep 2013 16:15:44 +0200 Subject: [PATCH 081/126] Limit the amount of prints during normal runs This removes some Debugf() calls and chages some direct prints to Debugf(). This means we don't get a bunch of spew when running the tests. --- devmapper/deviceset_devmapper.go | 3 ++- image.go | 10 +++------- runtime.go | 6 +++--- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go index 4544215b63..d163609a7f 100644 --- a/devmapper/deviceset_devmapper.go +++ b/devmapper/deviceset_devmapper.go @@ -1,6 +1,7 @@ package devmapper import ( + "github.com/dotcloud/docker/utils" "encoding/json" "fmt" "io" @@ -184,7 +185,7 @@ func (devices *DeviceSetDM) ensureImage(name string, size int64) (string, error) } func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) error { - log.Printf("Activating device-mapper pool %s", devices.getPoolName()) + utils.Debugf("Activating device-mapper pool %s", devices.getPoolName()) task, err := devices.createTask(DeviceCreate, devices.getPoolName()) if task == nil { return err diff --git a/image.go b/image.go index 546c54a577..081d28249a 100644 --- a/image.go +++ b/image.go @@ -339,7 +339,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { mounted, err := Mounted(mountDir) if err == nil && mounted { - log.Printf("Image %s is unexpectedly mounted, unmounting...", image.ID) + utils.Debugf("Image %s is unexpectedly mounted, unmounting...", image.ID) err = syscall.Unmount(mountDir, 0) if err != nil { return err @@ -347,21 +347,19 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { } if devices.HasDevice(image.ID) { - log.Printf("Found non-initialized demove-mapper device for image %s, removing", image.ID) + utils.Debugf("Found non-initialized demove-mapper device for image %s, removing", image.ID) err = devices.RemoveDevice(image.ID) if err != nil { return err } } - log.Printf("Creating device-mapper device for image id %s", image.ID) - + utils.Debugf("Creating device-mapper device for image id %s", image.ID) err = devices.AddDevice(image.ID, image.Parent) if err != nil { return err } - utils.Debugf("Mounting device %s at %s for image setup", image.ID, mountDir) err = devices.MountDevice(image.ID, mountDir) if err != nil { _ = devices.RemoveDevice(image.ID) @@ -375,14 +373,12 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } - utils.Debugf("Applying layer %s at %s", image.ID, mountDir) err = image.applyLayer(layerPath(root), mountDir) if err != nil { _ = devices.RemoveDevice(image.ID) return err } - utils.Debugf("Unmounting %s", mountDir) err = syscall.Unmount(mountDir, 0) if err != nil { _ = devices.RemoveDevice(image.ID) diff --git a/runtime.go b/runtime.go index 7d1c10a279..60046e29b0 100644 --- a/runtime.go +++ b/runtime.go @@ -107,15 +107,15 @@ func (runtime *Runtime) GetMountMethod() MountMethod { if runtime.mountMethod == MountMethodNone { // Try to automatically pick a method if hasFilesystemSupport("aufs") { - log.Printf("Using AUFS backend.") + utils.Debugf("Using AUFS backend.") runtime.mountMethod = MountMethodAUFS } else { _ = exec.Command("modprobe", "aufs").Run() if hasFilesystemSupport("aufs") { - log.Printf("Using AUFS backend.") + utils.Debugf("Using AUFS backend.") runtime.mountMethod = MountMethodAUFS } else { - log.Printf("Using device-mapper backend.") + utils.Debugf("Using device-mapper backend.") runtime.mountMethod = MountMethodDeviceMapper } } From 76a2ab6e34e6cdd66899815aeeaac2048e5bafce Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 10 Sep 2013 21:04:25 +0200 Subject: [PATCH 082/126] Allow specifying the docker client path in _DOCKER_INIT_PATH I currently need this to get the tests running, otherwise it will mount the docker.test binary inside the containers, which doesn't work due to the libdevmapper.so dependency. --- runtime.go | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/runtime.go b/runtime.go index 60046e29b0..2b776a5e01 100644 --- a/runtime.go +++ b/runtime.go @@ -52,16 +52,21 @@ type Runtime struct { var sysInitPath string func init() { - selfPath := utils.SelfPath() - - // If we have a separate docker-init, use that, otherwise use the - // main docker binary - dir := filepath.Dir(selfPath) - dockerInitPath := filepath.Join(dir, "docker-init") - if _, err := os.Stat(dockerInitPath); err != nil { - sysInitPath = selfPath + env := os.Getenv("_DOCKER_INIT_PATH") + if env != "" { + sysInitPath = env } else { - sysInitPath = dockerInitPath + selfPath := utils.SelfPath() + + // If we have a separate docker-init, use that, otherwise use the + // main docker binary + dir := filepath.Dir(selfPath) + dockerInitPath := filepath.Join(dir, "docker-init") + if _, err := os.Stat(dockerInitPath); err != nil { + sysInitPath = selfPath + } else { + sysInitPath = dockerInitPath + } } } From 7fb60caa5d34f8fc0abeae2ce7841000665414b0 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 11:46:16 +0200 Subject: [PATCH 083/126] tests: Store the loopback images for test outside unit-tests This directory is copied to each test prefix which is really slow with the large loopback mounts. --- runtime_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/runtime_test.go b/runtime_test.go index 5fa6c46bfe..6e5ca00518 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -23,6 +23,7 @@ const ( unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 unitTestNetworkBridge = "testdockbr0" unitTestStoreBase = "/var/lib/docker/unit-tests" + unitTestStoreDevicesBase = "/var/lib/docker/unit-tests-devices" testDaemonAddr = "127.0.0.1:4270" testDaemonProto = "tcp" ) @@ -88,7 +89,7 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreBase), false); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { panic(err) } else { globalRuntime = runtime From d951911b23b26cda087e823f203414a0ad184276 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 12:06:55 +0200 Subject: [PATCH 084/126] Always start tests from a clean set of loopback images This way we don't get any issues with leftovers --- runtime_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/runtime_test.go b/runtime_test.go index 6e5ca00518..5bf9af5fdb 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -88,6 +88,12 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge + // Always start from a clean set of loopback mounts + err := os.RemoveAll(unitTestStoreDevicesBase) + if err != nil { + panic(err) + } + // Make it our Store root if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { panic(err) From 9e64ebb29549db19a84b8cb514bea60c26184779 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 12:39:42 +0200 Subject: [PATCH 085/126] DeviceSet: Add UnmountDevice() Right now this does nothing but add a new layer, but it means that all DeviceMounts are paired with DeviceUnmounts so that we can track (and cleanup) active mounts. --- deviceset.go | 5 +++++ devmapper/deviceset_devmapper.go | 12 +++++++++++- image.go | 15 ++++++++------- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/deviceset.go b/deviceset.go index 2caaf153b2..95a3d48f27 100644 --- a/deviceset.go +++ b/deviceset.go @@ -6,6 +6,7 @@ type DeviceSet interface { DeactivateDevice(hash string) error RemoveDevice(hash string) error MountDevice(hash, path string) error + UnmountDevice(hash, path string) error HasDevice(hash string) bool HasInitializedDevice(hash string) bool } @@ -43,6 +44,10 @@ func (wrapper *DeviceSetWrapper) MountDevice(hash, path string) error { return wrapper.wrapped.MountDevice(wrapper.wrap(hash), path) } +func (wrapper *DeviceSetWrapper) UnmountDevice(hash, path string) error { + return wrapper.wrapped.UnmountDevice(wrapper.wrap(hash), path) +} + func (wrapper *DeviceSetWrapper) HasDevice(hash string) bool { return wrapper.wrapped.HasDevice(wrapper.wrap(hash)) } diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go index d163609a7f..e31515042c 100644 --- a/devmapper/deviceset_devmapper.go +++ b/devmapper/deviceset_devmapper.go @@ -637,7 +637,7 @@ func (devices *DeviceSetDM) setupBaseImage() error { return err } - err = syscall.Unmount(tmpDir, 0) + err = devices.UnmountDevice("", tmpDir) if err != nil { return err } @@ -840,6 +840,16 @@ func (devices *DeviceSetDM) MountDevice(hash, path string) error { return nil } +func (devices *DeviceSetDM) UnmountDevice(hash, path string) error { + err := syscall.Unmount(path, 0) + if err != nil { + return err + } + + return nil +} + + func (devices *DeviceSetDM) HasDevice(hash string) bool { if err := devices.ensureInit(); err != nil { return false diff --git a/image.go b/image.go index 081d28249a..da03fb8ff1 100644 --- a/image.go +++ b/image.go @@ -379,7 +379,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } - err = syscall.Unmount(mountDir, 0) + err = devices.UnmountDevice(image.ID, mountDir) if err != nil { _ = devices.RemoveDevice(image.ID) return err @@ -467,16 +467,17 @@ func (image *Image) Unmount(runtime *Runtime, root string, id string) error { return Unmount(root) case MountMethodDeviceMapper: - err := syscall.Unmount(root, 0) - if err != nil { - return err - } - // Try to deactivate the device as generally there is no use for it anymore devices, err := runtime.GetDeviceSet() if err != nil { return err; } + + err = devices.UnmountDevice(id, root) + if err != nil { + return err + } + return devices.DeactivateDevice(id) } return nil @@ -509,7 +510,7 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er } changes, err := ChangesDirs(root, rw) - _ = syscall.Unmount(rw, 0) + _ = devices.UnmountDevice(image.ID, rw) if err != nil { return nil, err } From ed741f7b27b1b1cf5b6f8917551ce86bc39e9c78 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 13:47:29 +0200 Subject: [PATCH 086/126] deviceset: Cleanup device sets on test end We unmount all mounts and deactivate all device mapper devices to make sure we're left with no leftovers after the test. --- deviceset.go | 5 +++ devmapper/deviceset_devmapper.go | 54 +++++++++++++++++++++++++++++--- runtime_test.go | 7 +++++ z_final_test.go | 2 +- 4 files changed, 63 insertions(+), 5 deletions(-) diff --git a/deviceset.go b/deviceset.go index 95a3d48f27..5dd608361f 100644 --- a/deviceset.go +++ b/deviceset.go @@ -9,6 +9,7 @@ type DeviceSet interface { UnmountDevice(hash, path string) error HasDevice(hash string) bool HasInitializedDevice(hash string) bool + Shutdown() error } type DeviceSetWrapper struct { @@ -36,6 +37,10 @@ func (wrapper *DeviceSetWrapper) DeactivateDevice(hash string) error { return wrapper.wrapped.DeactivateDevice(wrapper.wrap(hash)) } +func (wrapper *DeviceSetWrapper) Shutdown() error { + return nil +} + func (wrapper *DeviceSetWrapper) RemoveDevice(hash string) error { return wrapper.wrapped.RemoveDevice(wrapper.wrap(hash)) } diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go index e31515042c..4fcfac4464 100644 --- a/devmapper/deviceset_devmapper.go +++ b/devmapper/deviceset_devmapper.go @@ -40,6 +40,7 @@ type DeviceSetDM struct { TransactionId uint64 NewTransactionId uint64 nextFreeDevice int + activeMounts map[string]int } func getDevName(name string) string { @@ -348,8 +349,8 @@ func (devices *DeviceSetDM) deleteDevice(deviceId int) error { return nil } -func (devices *DeviceSetDM) removeDevice(info *DevInfo) error { - task, err := devices.createTask(DeviceRemove, info.Name()) +func (devices *DeviceSetDM) removeDevice(name string) error { + task, err := devices.createTask(DeviceRemove, name) if task == nil { return err } @@ -763,7 +764,7 @@ func (devices *DeviceSetDM) RemoveDevice(hash string) error { devinfo, _ := devices.getInfo(info.Name()) if devinfo != nil && devinfo.Exists != 0 { - err := devices.removeDevice(info) + err := devices.removeDevice(info.Name()) if err != nil { return err } @@ -809,7 +810,7 @@ func (devices *DeviceSetDM) DeactivateDevice(hash string) error { return err } if devinfo.Exists != 0 { - err := devices.removeDevice(info) + err := devices.removeDevice(info.Name()) if err != nil { return err } @@ -818,6 +819,39 @@ func (devices *DeviceSetDM) DeactivateDevice(hash string) error { return nil } +func (devices *DeviceSetDM) Shutdown() error { + if !devices.initialized { + return nil + } + + for path, count := range devices.activeMounts { + for i := count; i > 0; i-- { + err := syscall.Unmount(path, 0) + if err != nil { + fmt.Printf("Shutdown unmounting %s, error: %s\n", path, err) + } + } + delete(devices.activeMounts, path) + } + + for _, d := range devices.Devices { + if err := devices.DeactivateDevice(d.Hash); err != nil { + fmt.Printf("Shutdown deactivate %s , error: %s\n", d.Hash, err) + } + } + + + pool := devices.getPoolDevName() + devinfo, err := devices.getInfo(pool) + if err == nil && devinfo.Exists != 0 { + if err := devices.removeDevice(pool); err != nil { + fmt.Printf("Shutdown deactivate %s , error: %s\n", pool, err) + } + } + + return nil +} + func (devices *DeviceSetDM) MountDevice(hash, path string) error { if err := devices.ensureInit(); err != nil { return err @@ -837,6 +871,10 @@ func (devices *DeviceSetDM) MountDevice(hash, path string) error { if err != nil { return err } + + count := devices.activeMounts[path] + devices.activeMounts[path] = count + 1 + return nil } @@ -846,6 +884,13 @@ func (devices *DeviceSetDM) UnmountDevice(hash, path string) error { return err } + count := devices.activeMounts[path] + if count > 1 { + devices.activeMounts[path] = count - 1 + } else { + delete(devices.activeMounts, path) + } + return nil } @@ -913,6 +958,7 @@ func NewDeviceSetDM(root string) *DeviceSetDM { devicePrefix: base, } devices.Devices = make(map[string]*DevInfo) + devices.activeMounts = make(map[string]int) return devices } diff --git a/runtime_test.go b/runtime_test.go index 5bf9af5fdb..ccdf4d9563 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -64,6 +64,13 @@ func cleanup(runtime *Runtime) error { return nil } +func cleanupLast(runtime *Runtime) error { + cleanup(runtime) + runtime.deviceSet.Shutdown() + return nil +} + + func layerArchive(tarfile string) (io.Reader, error) { // FIXME: need to close f somewhere f, err := os.Open(tarfile) diff --git a/z_final_test.go b/z_final_test.go index 08a180baaf..c52f87cddb 100644 --- a/z_final_test.go +++ b/z_final_test.go @@ -11,7 +11,7 @@ func displayFdGoroutines(t *testing.T) { } func TestFinal(t *testing.T) { - cleanup(globalRuntime) + cleanupLast(globalRuntime) t.Logf("Start Fds: %d, Start Goroutines: %d", startFds, startGoroutines) displayFdGoroutines(t) } From 0d7ab8db03814bac19fe076a0d53b6d423616bc7 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 13:48:58 +0200 Subject: [PATCH 087/126] graph test: Unmount image via image.Unmount() This helps us track the unmount --- graph_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph_test.go b/graph_test.go index d89e6efe9d..ecb5ffb34e 100644 --- a/graph_test.go +++ b/graph_test.go @@ -152,7 +152,7 @@ func TestMount(t *testing.T) { } // FIXME: test for mount contents defer func() { - if err := Unmount(rootfs); err != nil { + if err := image.Unmount(runtime, rootfs, "testing"); err != nil { t.Error(err) } }() From 5f8e24f8428bc5fb7076ec757ded44f965c48888 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 14:18:20 +0200 Subject: [PATCH 088/126] Runtime: Only remove device on destroy if it exists --- runtime.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime.go b/runtime.go index 2b776a5e01..3bbed75295 100644 --- a/runtime.go +++ b/runtime.go @@ -288,7 +288,7 @@ func (runtime *Runtime) Destroy(container *Container) error { if err := os.RemoveAll(container.root); err != nil { return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err) } - if runtime.GetMountMethod() == MountMethodDeviceMapper { + if runtime.GetMountMethod() == MountMethodDeviceMapper && runtime.deviceSet.HasDevice(container.ID) { if err := runtime.deviceSet.RemoveDevice(container.ID); err != nil { return fmt.Errorf("Unable to remove device for %v: %v", container.ID, err) } From 07227866006ea75a0c492814b808fdbb672431ee Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 14:44:19 +0200 Subject: [PATCH 089/126] api_test: Fix PostContainersCreate We can't look for the created file in the rwpath, because that doesn't exist in the device-mapper world, instead look in the RootfsPath. --- api_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/api_test.go b/api_test.go index bcf662e9e7..ae8602882f 100644 --- a/api_test.go +++ b/api_test.go @@ -646,13 +646,21 @@ func TestPostContainersCreate(t *testing.T) { t.Fatal(err) } - if _, err := os.Stat(path.Join(container.rwPath(), "test")); err != nil { + if err := container.EnsureMounted(); err != nil { + t.Fatalf("Unable to mount container: %s", err) + } + + if _, err := os.Stat(path.Join(container.RootfsPath(), "test")); err != nil { if os.IsNotExist(err) { utils.Debugf("Err: %s", err) t.Fatalf("The test file has not been created") } t.Fatal(err) } + + if err := container.Unmount(); err != nil { + t.Fatalf("Unable to unmount container: %s", err) + } } func TestPostContainersKill(t *testing.T) { From c1388010731cac1c8ff5159bd72b926545a64e58 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Mon, 9 Sep 2013 15:56:59 +0200 Subject: [PATCH 090/126] Container: Inject into the mount, not the rwPath For device-mapper setups we can't just push the file into the rwPath. --- container.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/container.go b/container.go index cd143f7236..002e2eb31e 100644 --- a/container.go +++ b/container.go @@ -297,12 +297,17 @@ func (settings *NetworkSettings) PortMappingAPI() []APIPort { // Inject the io.Reader at the given path. Note: do not close the reader func (container *Container) Inject(file io.Reader, pth string) error { - // Make sure the directory exists - if err := os.MkdirAll(path.Join(container.rwPath(), path.Dir(pth)), 0755); err != nil { + if err := container.EnsureMounted(); err != nil { return err } + + // Make sure the directory exists + if err := os.MkdirAll(path.Join(container.RootfsPath(), path.Dir(pth)), 0755); err != nil { + return err + } + // FIXME: Handle permissions/already existing dest - dest, err := os.Create(path.Join(container.rwPath(), pth)) + dest, err := os.Create(path.Join(container.RootfsPath(), pth)) if err != nil { return err } From 145024c6ccc7bff1eda632823702f91899667ed3 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 12 Sep 2013 15:17:39 +0200 Subject: [PATCH 091/126] Utils: Add ShellQuoteArguments --- utils/utils.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/utils/utils.go b/utils/utils.go index d417690c0c..94aa0dc902 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1021,3 +1021,36 @@ type StatusError struct { func (e *StatusError) Error() string { return fmt.Sprintf("Status: %d", e.Status) } + +func quote(word string, buf *bytes.Buffer) { + // Bail out early for "simple" strings + if word != "" && !strings.ContainsAny(word, "\\'\"`${[|&;<>()~*?! \t\n") { + buf.WriteString(word) + return + } + + buf.WriteString("''") + + for i := 0; i < len(word); i++ { + b := word[i] + if b == '\'' { + // Replace literal ' with a close ', a \', and a open ' + buf.WriteString("'\\''") + } else { + buf.WriteByte(b) + } + } + + buf.WriteString("''") +} + +func ShellQuoteArguments(args []string) string { + var buf bytes.Buffer + for i, arg := range args { + if i != 0 { + buf.WriteByte(' ') + } + quote(arg, &buf) + } + return buf.String() +} From 429587779a95a4d38ec9cd66202de9729c320ef8 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 12 Sep 2013 15:18:11 +0200 Subject: [PATCH 092/126] lxc: Work around lxc-start need for private mounts lxc-start requires / to be mounted private, otherwise the changes it does inside the container (both mounts and unmounts) will propagate out to the host. We work around this by starting up lxc-start in its own namespace where we set / to rprivate. Unfortunately go can't really execute any code between clone and exec, so we can't do this in a nice way. Instead we have a horrible hack that use the unshare command, the shell and the mount command... --- container.go | 17 ++++++++++++++++- utils.go | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/container.go b/container.go index 002e2eb31e..35060e873e 100644 --- a/container.go +++ b/container.go @@ -748,6 +748,7 @@ func (container *Container) Start(hostConfig *HostConfig) error { } params := []string{ + "lxc-start", "-n", container.ID, "-f", container.lxcConfigPath(), "--", @@ -796,7 +797,21 @@ func (container *Container) Start(hostConfig *HostConfig) error { params = append(params, "--", container.Path) params = append(params, container.Args...) - container.cmd = exec.Command("lxc-start", params...) + if RootIsShared() { + // lxc-start really needs / to be private, or all kinds of stuff break + // What we really want is to clone into a new namespace and then + // mount / MS_REC|MS_PRIVATE, but since we can't really clone or fork + // without exec in go we have to do this horrible shell hack... + shellString := + "mount --make-rprivate /; exec " + + utils.ShellQuoteArguments(params) + + params = []string{ + "unshare", "-m", "--", "/bin/sh", "-c", shellString, + } + } + + container.cmd = exec.Command(params[0], params[1:]...) // Setup logging of stdout and stderr to disk if err := container.runtime.LogToDisk(container.stdout, container.logPath("json"), "stdout"); err != nil { diff --git a/utils.go b/utils.go index aed8ffdd76..babca65bf1 100644 --- a/utils.go +++ b/utils.go @@ -2,6 +2,7 @@ package docker import ( "fmt" + "io/ioutil" "strings" ) @@ -167,3 +168,17 @@ func parseLxcOpt(opt string) (string, string, error) { } return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil } + +func RootIsShared() bool { + if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { + for _, line := range strings.Split(string(data), "\n") { + cols := strings.Split(line, " ") + if cols[3] == "/" && cols[4] == "/" { + return strings.HasPrefix(cols[6], "shared") + } + } + } + + // No idea, probably safe to assume so + return true +} From fdbc2695fe00d522c5c1a962f9be2f802bf53943 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 12 Sep 2013 20:30:55 +0200 Subject: [PATCH 093/126] devmapper: Move init layer to top rather than bottom The init layer needs to be topmost to make sure certain files are always there (for instance, the ubuntu:12.10 image wrongly has /dev/shm being a symlink to /run/shm, and we need to override that). However, previously the devmapper code implemented the init layer by putting it in the base devmapper device, which meant layers above it could override these files (so that ubuntu:12.10 broke). So, instead we put the base layer in *each* images devmapper device. This is "safe" because we still have the pristine layer data in the layer directory. Also, it means we diff the container against the image with the init layer applied, so it won't show up in diffs/commits. --- devmapper/deviceset_devmapper.go | 62 -------------------------------- image.go | 17 +++++++++ 2 files changed, 17 insertions(+), 62 deletions(-) diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go index 4fcfac4464..670d7621c4 100644 --- a/devmapper/deviceset_devmapper.go +++ b/devmapper/deviceset_devmapper.go @@ -542,45 +542,6 @@ func (devices *DeviceSetDM) loadMetaData() error { return nil } -func (devices *DeviceSetDM) createBaseLayer(dir string) error { - for pth, typ := range map[string]string{ - "/dev/pts": "dir", - "/dev/shm": "dir", - "/proc": "dir", - "/sys": "dir", - "/.dockerinit": "file", - "/etc/resolv.conf": "file", - "/etc/hosts": "file", - "/etc/hostname": "file", - // "var/run": "dir", - // "var/lock": "dir", - } { - if _, err := os.Stat(path.Join(dir, pth)); err != nil { - if os.IsNotExist(err) { - switch typ { - case "dir": - if err := os.MkdirAll(path.Join(dir, pth), 0755); err != nil { - return err - } - case "file": - if err := os.MkdirAll(path.Join(dir, path.Dir(pth)), 0755); err != nil { - return err - } - - if f, err := os.OpenFile(path.Join(dir, pth), os.O_CREATE, 0755); err != nil { - return err - } else { - f.Close() - } - } - } else { - return err - } - } - } - return nil -} - func (devices *DeviceSetDM) setupBaseImage() error { oldInfo := devices.Devices[""] if oldInfo != nil && oldInfo.Initialized { @@ -622,29 +583,6 @@ func (devices *DeviceSetDM) setupBaseImage() error { return err } - tmpDir := path.Join(devices.loopbackDir(), "basefs") - if err = os.MkdirAll(tmpDir, 0700); err != nil && !os.IsExist(err) { - return err - } - - err = devices.MountDevice("", tmpDir) - if err != nil { - return err - } - - err = devices.createBaseLayer(tmpDir) - if err != nil { - _ = syscall.Unmount(tmpDir, 0) - return err - } - - err = devices.UnmountDevice("", tmpDir) - if err != nil { - return err - } - - _ = os.Remove(tmpDir) - info.Initialized = true err = devices.saveMetadata() diff --git a/image.go b/image.go index da03fb8ff1..1a279654d5 100644 --- a/image.go +++ b/image.go @@ -379,6 +379,23 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } + // The docker init layer is conceptually above all other layers, so we apply + // it for every image. This is safe because the layer directory is the + // definition of the image, and the device-mapper device is just a cache + // of it instantiated. Diffs/commit compare the container device with the + // image device, which will then *not* pick up the init layer changes as + // part of the container changes + dockerinitLayer, err := image.getDockerInitLayer() + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + err = image.applyLayer(dockerinitLayer, mountDir) + if err != nil { + _ = devices.RemoveDevice(image.ID) + return err + } + err = devices.UnmountDevice(image.ID, mountDir) if err != nil { _ = devices.RemoveDevice(image.ID) From 7d566b4f761e8942cf9679e96774b320b8496b2f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 11:08:59 +0200 Subject: [PATCH 094/126] RootIsShared() - Fix array out of bounds error This happened for me on the last (empty) line, but better safe than sorry so we make the check general. --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index babca65bf1..57737f7b25 100644 --- a/utils.go +++ b/utils.go @@ -173,7 +173,7 @@ func RootIsShared() bool { if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { for _, line := range strings.Split(string(data), "\n") { cols := strings.Split(line, " ") - if cols[3] == "/" && cols[4] == "/" { + if len(cols) >= 6 && cols[3] == "/" && cols[4] == "/" { return strings.HasPrefix(cols[6], "shared") } } From 727e7fcccadf1d3e286f5a3c8d1aa388f6b4dab8 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 12:56:58 +0200 Subject: [PATCH 095/126] Change how ChangesDirs() works Rather than scan the files in the old directory twice to detect the deletions we now scan both directories twice and then do all the diffing on the in-memory structure. This is more efficient, but it also lets us diff more complex things later that are not exact on-disk trees. --- changes.go | 201 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 143 insertions(+), 58 deletions(-) diff --git a/changes.go b/changes.go index 00c9cc7c77..d1b0a25b0d 100644 --- a/changes.go +++ b/changes.go @@ -106,50 +106,85 @@ func ChangesAUFS(layers []string, rw string) ([]Change, error) { return changes, nil } -func ChangesDirs(newDir, oldDir string) ([]Change, error) { - var changes []Change - err := filepath.Walk(newDir, func(newPath string, f os.FileInfo, err error) error { - if err != nil { - return err - } +type FileInfo struct { + parent *FileInfo + name string + stat syscall.Stat_t + children map[string]*FileInfo +} - var newStat syscall.Stat_t - err = syscall.Lstat(newPath, &newStat) - if err != nil { - return err - } +func (root *FileInfo) LookUp(path string) *FileInfo { + parent := root + if path == "/" { + return root + } - // Rebase path - relPath, err := filepath.Rel(newDir, newPath) - if err != nil { - return err - } - relPath = filepath.Join("/", relPath) - - // Skip root - if relPath == "/" || relPath == "/.docker-id" { - return nil - } - - change := Change{ - Path: relPath, - } - - oldPath := filepath.Join(oldDir, relPath) - - var oldStat = &syscall.Stat_t{} - err = syscall.Lstat(oldPath, oldStat) - if err != nil { - if !os.IsNotExist(err) { - return err + pathElements := strings.Split(path, "/") + for _, elem := range pathElements { + if elem != "" { + child := parent.children[elem] + if child == nil { + return nil } - oldStat = nil + parent = child } + } + return parent +} - if oldStat == nil { - change.Kind = ChangeAdd - changes = append(changes, change) - } else { +func (info *FileInfo)path() string { + if info.parent == nil { + return "/" + } + return filepath.Join(info.parent.path(), info.name) +} + +func (info *FileInfo)unlink() { + if info.parent != nil { + delete(info.parent.children, info.name) + } +} + +func (info *FileInfo)Remove(path string) bool { + child := info.LookUp(path) + if child != nil { + child.unlink() + return true + } + return false +} + +func (info *FileInfo)isDir() bool { + return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR +} + + +func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { + if oldInfo == nil { + // add + change := Change{ + Path: info.path(), + Kind: ChangeAdd, + } + *changes = append(*changes, change) + } + + // We make a copy so we can modify it to detect additions + // also, we only recurse on the old dir if the new info is a directory + // otherwise any previous delete/change is considered recursive + oldChildren := make(map[string]*FileInfo) + if oldInfo != nil && info.isDir() { + for k, v := range oldInfo.children { + oldChildren[k] = v + } + } + + for name, newChild := range info.children { + oldChild, _ := oldChildren[name] + if oldChild != nil { + // change? + oldStat := &oldChild.stat + newStat := &newChild.stat if oldStat.Ino != newStat.Ino || oldStat.Mode != newStat.Mode || oldStat.Uid != newStat.Uid || @@ -159,50 +194,100 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) { oldStat.Blocks != newStat.Blocks || oldStat.Mtim != newStat.Mtim || oldStat.Ctim != newStat.Ctim { - change.Kind = ChangeModify - changes = append(changes, change) + change := Change{ + Path: newChild.path(), + Kind: ChangeModify, + } + *changes = append(*changes, change) } + + // Remove from copy so we can detect deletions + delete(oldChildren, name) } - return nil - }) - if err != nil { - return nil, err + newChild.addChanges(oldChild, changes) } - err = filepath.Walk(oldDir, func(oldPath string, f os.FileInfo, err error) error { + for _, oldChild := range oldChildren { + // delete + change := Change{ + Path: oldChild.path(), + Kind: ChangeDelete, + } + *changes = append(*changes, change) + } + + +} + +func (info *FileInfo)Changes(oldInfo *FileInfo) []Change { + var changes []Change + + info.addChanges(oldInfo, &changes) + + return changes +} + + +func collectFileInfo(sourceDir string) (*FileInfo, error) { + root := &FileInfo { + name: "/", + children: make(map[string]*FileInfo), + } + + err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error { if err != nil { return err } // Rebase path - relPath, err := filepath.Rel(oldDir, oldPath) + relPath, err := filepath.Rel(sourceDir, path) if err != nil { return err } relPath = filepath.Join("/", relPath) - // Skip root if relPath == "/" { return nil } - change := Change{ - Path: relPath, + parent := root.LookUp(filepath.Dir(relPath)) + if parent == nil { + return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) } - newPath := filepath.Join(newDir, relPath) - - var newStat = &syscall.Stat_t{} - err = syscall.Lstat(newPath, newStat) - if err != nil && os.IsNotExist(err) { - change.Kind = ChangeDelete - changes = append(changes, change) + info := &FileInfo { + name: filepath.Base(relPath), + children: make(map[string]*FileInfo), + parent: parent, } + if err := syscall.Lstat(path, &info.stat); err != nil { + return err + } + + parent.children[info.name] = info + return nil }) if err != nil { return nil, err } - return changes, nil + return root, nil +} + +func ChangesDirs(newDir, oldDir string) ([]Change, error) { + oldRoot, err := collectFileInfo(oldDir) + if err != nil { + return nil, err + } + newRoot, err := collectFileInfo(newDir) + if err != nil { + return nil, err + } + + // Ignore changes in .docker-id + _ = newRoot.Remove("/.docker-id") + _ = oldRoot.Remove("/.docker-id") + + return newRoot.Changes(oldRoot), nil } From 5d2ace3424516bd7cc8d4a57fcaddd00fa1c4b5d Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 15:45:58 +0200 Subject: [PATCH 096/126] Image.applyLayer: Be better at creating identical files There are some changes here that make the file metadata better match the layer files: * Set the mode of the file after the chown, as otherwise the per-group/uid specific flags and e.g. sticky bit is lost * Use lchown instead of chown * Delay mtime updates to after all other changes so that later file creation doesn't change the mtime for the parent directory * Use Futimes in combination with O_PATH|O_NOFOLLOW to set mtime on symlinks --- image.go | 55 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/image.go b/image.go index 1a279654d5..61ca83b208 100644 --- a/image.go +++ b/image.go @@ -175,7 +175,13 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) { return Tar(layerPath, compression) } +type TimeUpdate struct { + path string + time []syscall.Timeval +} + func (image *Image) applyLayer(layer, target string) error { + var updateTimes []TimeUpdate oldmask := syscall.Umask(0) defer syscall.Umask(oldmask) err := filepath.Walk(layer, func(srcPath string, f os.FileInfo, err error) error { @@ -249,11 +255,6 @@ func (image *Image) applyLayer(layer, target string) error { if err != nil { return err } - } else if srcStat.Mode&07777 != targetStat.Mode&07777 { - err = syscall.Chmod(targetPath, srcStat.Mode&07777) - if err != nil { - return err - } } } else if srcStat.Mode&syscall.S_IFLNK == syscall.S_IFLNK { // Source is symlink @@ -293,22 +294,52 @@ func (image *Image) applyLayer(layer, target string) error { return fmt.Errorf("Unknown type for file %s", srcPath) } + err = syscall.Lchown(targetPath, int(srcStat.Uid), int(srcStat.Gid)) + if err != nil { + return err + } + if srcStat.Mode&syscall.S_IFLNK != syscall.S_IFLNK { - err = syscall.Chown(targetPath, int(srcStat.Uid), int(srcStat.Gid)) + err = syscall.Chmod(targetPath, srcStat.Mode&07777) if err != nil { return err } - ts := []syscall.Timeval{ - syscall.NsecToTimeval(srcStat.Atim.Nano()), - syscall.NsecToTimeval(srcStat.Mtim.Nano()), - } - syscall.Utimes(targetPath, ts) } + ts := []syscall.Timeval{ + syscall.NsecToTimeval(srcStat.Atim.Nano()), + syscall.NsecToTimeval(srcStat.Mtim.Nano()), + } + + u := TimeUpdate { + path: targetPath, + time: ts, + } + + // Delay time updates until all other changes done, or it is + // overwritten for directories (by child changes) + updateTimes = append(updateTimes, u) } return nil }) - return err + if err != nil { + return err + } + + // We do this in reverse order so that children are updated before parents + for i := len(updateTimes) - 1; i >= 0; i-- { + update := updateTimes[i] + + O_PATH := 010000000 // Not in syscall yet + fd, err := syscall.Open(update.path, syscall.O_RDWR | O_PATH | syscall.O_NOFOLLOW, 0600) + if err != nil { + return err + } + syscall.Futimes(fd, update.time) + _ = syscall.Close(fd) + } + + return nil } func (image *Image) ensureImageDevice(devices DeviceSet) error { From ad402763e160ded1924c9b154983d81614e90deb Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 15:49:57 +0200 Subject: [PATCH 097/126] Changes: Better metadata comparison Change the comparison to better handle files that are copied during container creation but not actually changed: * Inode - this will change during a copy * ctime - this will change during a copy (as we can't set it back) * blocksize - this will change for sparse files during copy * size for directories - this can change anytime but doesn't necessarily reflect an actual contents change * Compare mtimes at microsecond precision (as this is what utimes has) --- changes.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/changes.go b/changes.go index d1b0a25b0d..53fa2d49b4 100644 --- a/changes.go +++ b/changes.go @@ -185,15 +185,22 @@ func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { // change? oldStat := &oldChild.stat newStat := &newChild.stat - if oldStat.Ino != newStat.Ino || - oldStat.Mode != newStat.Mode || + // Note: We can't compare inode or ctime or blocksize here, because these change + // when copying a file into a container. However, that is not generally a problem + // because any content change will change mtime, and any status change should + // be visible when actually comparing the stat fields. The only time this + // breaks down is if some code intentionally hides a change by setting + // back mtime + oldMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano()) + newMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano()) + if oldStat.Mode != newStat.Mode || oldStat.Uid != newStat.Uid || oldStat.Gid != newStat.Gid || oldStat.Rdev != newStat.Rdev || - oldStat.Size != newStat.Size || - oldStat.Blocks != newStat.Blocks || - oldStat.Mtim != newStat.Mtim || - oldStat.Ctim != newStat.Ctim { + // Don't look at size for dirs, its not a good measure of change + (oldStat.Size != newStat.Size && oldStat.Mode &syscall.S_IFDIR != syscall.S_IFDIR) || + oldMtime.Sec != newMtime.Sec || + oldMtime.Usec != newMtime.Usec { change := Change{ Path: newChild.path(), Kind: ChangeModify, From 60f552cac3d165dbe4c8d65c1ab82d31700dcc5d Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 15:56:06 +0200 Subject: [PATCH 098/126] Add Changes.ChangesLayers() This calculates the difference between a set of layers and a directory tree. --- changes.go | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/changes.go b/changes.go index 53fa2d49b4..49802d3170 100644 --- a/changes.go +++ b/changes.go @@ -235,11 +235,88 @@ func (info *FileInfo)Changes(oldInfo *FileInfo) []Change { } -func collectFileInfo(sourceDir string) (*FileInfo, error) { +func newRootFileInfo() *FileInfo { root := &FileInfo { name: "/", children: make(map[string]*FileInfo), } + return root +} + +func applyLayer(root *FileInfo, layer string) error { + err := filepath.Walk(layer, func(layerPath string, f os.FileInfo, err error) error { + if err != nil { + return err + } + + // Skip root + if layerPath == layer { + return nil + } + + // rebase path + relPath, err := filepath.Rel(layer, layerPath) + if err != nil { + return err + } + relPath = filepath.Join("/", relPath) + + // Skip AUFS metadata + if matched, err := filepath.Match("/.wh..wh.*", relPath); err != nil || matched { + if err != nil || !f.IsDir() { + return err + } + return filepath.SkipDir + } + + var layerStat syscall.Stat_t + err = syscall.Lstat(layerPath, &layerStat) + if err != nil { + return err + } + + file := filepath.Base(relPath) + // If there is a whiteout, then the file was removed + if strings.HasPrefix(file, ".wh.") { + originalFile := file[len(".wh."):] + deletePath := filepath.Join(filepath.Dir(relPath), originalFile) + + root.Remove(deletePath) + } else { + // Added or changed file + existing := root.LookUp(relPath) + if existing != nil { + // Changed file + existing.stat = layerStat + if !existing.isDir() { + // Changed from dir to non-dir, delete all previous files + existing.children = make(map[string]*FileInfo) + } + } else { + // Added file + parent := root.LookUp(filepath.Dir(relPath)) + if parent == nil { + return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) + } + + info := &FileInfo { + name: filepath.Base(relPath), + children: make(map[string]*FileInfo), + parent: parent, + stat: layerStat, + } + + parent.children[info.name] = info + } + } + return nil + }) + return err +} + + +func collectFileInfo(sourceDir string) (*FileInfo, error) { + root := newRootFileInfo() err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error { if err != nil { @@ -282,6 +359,22 @@ func collectFileInfo(sourceDir string) (*FileInfo, error) { return root, nil } +func ChangesLayers(newDir string, layers []string) ([]Change, error) { + newRoot, err := collectFileInfo(newDir) + if err != nil { + return nil, err + } + oldRoot := newRootFileInfo() + for i := len(layers)-1; i >= 0; i-- { + layer := layers[i] + if err = applyLayer(oldRoot, layer); err != nil { + return nil, err + } + } + + return newRoot.Changes(oldRoot), nil +} + func ChangesDirs(newDir, oldDir string) ([]Change, error) { oldRoot, err := collectFileInfo(oldDir) if err != nil { From 43a7d3d0e9e2fafcdc90cb84855e1bb3869d2729 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 15:58:14 +0200 Subject: [PATCH 099/126] Add trivial copy-based CoW backend This creates a container by copying the corresponding files from the layers into the containers. This is not gonna be very useful on a developer setup, as there is no copy-on-write or general diskspace sharing. It also makes container instantiation slower. However, it may be useful in deployment where we don't always have a lot of containers running (long-running daemons) and where we don't do a lot of docker commits. --- container.go | 6 ++++- image.go | 68 ++++++++++++++++++++++++++++++++++++++++++++++++---- runtime.go | 2 ++ 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/container.go b/container.go index 35060e873e..e9fb4890b7 100644 --- a/container.go +++ b/container.go @@ -1209,7 +1209,11 @@ func (container *Container) GetImage() (*Image, error) { } func (container *Container) Mounted() (bool, error) { - return Mounted(container.RootfsPath()) + image, err := container.GetImage() + if err != nil { + return false, err + } + return image.Mounted(container.runtime, container.RootfsPath(), container.rwPath()) } func (container *Container) Unmount() error { diff --git a/image.go b/image.go index 61ca83b208..2c16aeb6d4 100644 --- a/image.go +++ b/image.go @@ -442,16 +442,38 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return nil } +func (image *Image) Mounted(runtime *Runtime, root, rw string) (bool, error) { + method := runtime.GetMountMethod() + if method == MountMethodFilesystem { + if _, err := os.Stat(rw); err != nil { + if os.IsNotExist(err) { + err = nil + } + return false, err + } + mountedPath := path.Join(rw, ".fs-mounted") + if _, err := os.Stat(mountedPath); err != nil { + if os.IsNotExist(err) { + err = nil + } + return false, err + } + return true, nil + } else { + return Mounted(root) + } +} + func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { - if mounted, err := Mounted(root); err != nil { - return err - } else if mounted { + if mounted, _ := image.Mounted(runtime, root, rw); mounted { return fmt.Errorf("%s is already mounted", root) } + // Create the target directories if they don't exist if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) { return err } + switch runtime.GetMountMethod() { case MountMethodNone: return fmt.Errorf("No supported Mount implementation") @@ -502,6 +524,29 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { } } + case MountMethodFilesystem: + if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { + return err + } + + layers, err := image.layers() + if err != nil { + return err + } + + for i := len(layers)-1; i >= 0; i-- { + layer := layers[i] + if err = image.applyLayer(layer, root); err != nil { + return err + } + } + + mountedPath := path.Join(rw, ".fs-mounted") + fo, err := os.Create(mountedPath) + if err != nil { + return err + } + fo.Close() } return nil } @@ -527,7 +572,11 @@ func (image *Image) Unmount(runtime *Runtime, root string, id string) error { } return devices.DeactivateDevice(id) + + case MountMethodFilesystem: + return nil } + return nil } @@ -563,6 +612,17 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er return nil, err } return changes, nil + + case MountMethodFilesystem: + layers, err := image.layers() + if err != nil { + return nil, err + } + changes, err := ChangesLayers(root, layers) + if err != nil { + return nil, err + } + return changes, nil } return nil, fmt.Errorf("No supported Changes implementation") @@ -573,7 +633,7 @@ func (image *Image) ExportChanges(runtime *Runtime, root, rw, id string) (Archiv case MountMethodAUFS: return Tar(rw, Uncompressed) - case MountMethodDeviceMapper: + case MountMethodFilesystem, MountMethodDeviceMapper: changes, err := image.Changes(runtime, root, rw, id) if err != nil { return nil, err diff --git a/runtime.go b/runtime.go index 3bbed75295..28263ad7cb 100644 --- a/runtime.go +++ b/runtime.go @@ -23,6 +23,7 @@ const ( MountMethodNone MountMethod = iota MountMethodAUFS MountMethodDeviceMapper + MountMethodFilesystem ) type Capabilities struct { @@ -124,6 +125,7 @@ func (runtime *Runtime) GetMountMethod() MountMethod { runtime.mountMethod = MountMethodDeviceMapper } } + runtime.mountMethod = MountMethodFilesystem } return runtime.mountMethod From 91c69fd3532eaa92cfc21c6331699a995fdbe2a6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 16:04:07 +0200 Subject: [PATCH 100/126] Remove accidental commit that enabled MountMethodFilesystem --- runtime.go | 1 - 1 file changed, 1 deletion(-) diff --git a/runtime.go b/runtime.go index 28263ad7cb..10e937e21a 100644 --- a/runtime.go +++ b/runtime.go @@ -125,7 +125,6 @@ func (runtime *Runtime) GetMountMethod() MountMethod { runtime.mountMethod = MountMethodDeviceMapper } } - runtime.mountMethod = MountMethodFilesystem } return runtime.mountMethod From 86421e8b5e84452d66e6b16b5cb1a5a438f8fa1f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 16:35:56 +0200 Subject: [PATCH 101/126] Add CopyFile that can use btrfs reflinks if availible --- utils.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/utils.go b/utils.go index 57737f7b25..44573927a1 100644 --- a/utils.go +++ b/utils.go @@ -1,9 +1,33 @@ package docker +/* +#include +#include +#include + +// See linux.git/fs/btrfs/ioctl.h +#define BTRFS_IOCTL_MAGIC 0x94 +#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int) + +int +btrfs_reflink(int fd_out, int fd_in) +{ + int res; + res = ioctl(fd_out, BTRFS_IOC_CLONE, fd_in); + if (res < 0) + return errno; + return 0; +} + +*/ +import "C" import ( "fmt" + "io" "io/ioutil" + "os" "strings" + "syscall" ) // Compare two Config struct. Do not compare the "Image" nor "Hostname" fields @@ -182,3 +206,22 @@ func RootIsShared() bool { // No idea, probably safe to assume so return true } + +func BtrfsReflink(fd_out, fd_in uintptr) error { + res := C.btrfs_reflink(C.int(fd_out), C.int(fd_in)) + if res != 0 { + return syscall.Errno(res) + } + return nil +} + +func CopyFile(dstFile, srcFile *os.File) error { + err := BtrfsReflink(dstFile.Fd(), srcFile.Fd()) + if err == nil { + return nil + } + + // Fall back to normal copy + _, err = io.Copy(dstFile, srcFile) + return err +} From 1a082ed245aaf55cc92e884c988865b195ea0cb7 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 13 Sep 2013 16:36:32 +0200 Subject: [PATCH 102/126] applyLayer() use btrfs reflinks if availible We use the new file copy helper which uses btrfs reflinks if availible. --- image.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/image.go b/image.go index 2c16aeb6d4..7819873757 100644 --- a/image.go +++ b/image.go @@ -284,12 +284,16 @@ func (image *Image) applyLayer(layer, target string) error { } dstFile := os.NewFile(uintptr(fd), targetPath) srcFile, err := os.Open(srcPath) - _, err = io.Copy(dstFile, srcFile) + if err != nil { + _ = dstFile.Close() + return err + } + err = CopyFile(dstFile, srcFile) + _ = dstFile.Close() + _ = srcFile.Close() if err != nil { return err } - _ = srcFile.Close() - _ = dstFile.Close() } else { return fmt.Errorf("Unknown type for file %s", srcPath) } From bbc72c85f7733fefda499cb0e96f1dda2ce90625 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 14:39:47 +0200 Subject: [PATCH 103/126] devmapper: Fix loopback mount code Typo in the loop-control code made it always fall back to the old method of opening loopback devices. --- devmapper/devmapper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devmapper/devmapper.go b/devmapper/devmapper.go index f007091827..75742628a2 100644 --- a/devmapper/devmapper.go +++ b/devmapper/devmapper.go @@ -27,7 +27,7 @@ attach_loop_device(const char *filename, int *loop_fd_out) start_index = 0; fd = open("/dev/loop-control", O_RDONLY); - if (fd == 0) { + if (fd >= 0) { start_index = ioctl(fd, LOOP_CTL_GET_FREE); close(fd); From 2c71710b74829787a0c78f7e02f45d31935a996f Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 14:57:22 +0200 Subject: [PATCH 104/126] image: Handle systems that don't support O_PATH when updating timestamp Older kernel can't handle O_PATH in open() so this will fail on dirs and symlinks. For dirs wa can fallback to the normal Utimes, but for symlinks there is not much to do but ignore their timestamps. --- image.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/image.go b/image.go index 7819873757..b3e8de3e55 100644 --- a/image.go +++ b/image.go @@ -336,11 +336,21 @@ func (image *Image) applyLayer(layer, target string) error { O_PATH := 010000000 // Not in syscall yet fd, err := syscall.Open(update.path, syscall.O_RDWR | O_PATH | syscall.O_NOFOLLOW, 0600) - if err != nil { - return err + if err == syscall.EISDIR || err == syscall.ELOOP { + // O_PATH not supported, use Utimes except on symlinks where Utimes doesn't work + if err != syscall.ELOOP { + err = syscall.Utimes(update.path, update.time) + if err != nil { + return err + } + } + } else { + if err != nil { + return err + } + syscall.Futimes(fd, update.time) + _ = syscall.Close(fd) } - syscall.Futimes(fd, update.time) - _ = syscall.Close(fd) } return nil From ecdbdfdaea5497c34a0299e7efd97b8a44bd7314 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 14:59:27 +0200 Subject: [PATCH 105/126] Image: unmount device before removing it on failures If we don't do this the remove will fail due to EBUSY --- image.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/image.go b/image.go index b3e8de3e55..16abf9eef0 100644 --- a/image.go +++ b/image.go @@ -414,6 +414,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { err = ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600) if err != nil { + _ = devices.UnmountDevice(image.ID, mountDir) _ = devices.RemoveDevice(image.ID) return err } @@ -432,11 +433,13 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { // part of the container changes dockerinitLayer, err := image.getDockerInitLayer() if err != nil { + _ = devices.UnmountDevice(image.ID, mountDir) _ = devices.RemoveDevice(image.ID) return err } err = image.applyLayer(dockerinitLayer, mountDir) if err != nil { + _ = devices.UnmountDevice(image.ID, mountDir) _ = devices.RemoveDevice(image.ID) return err } From a7e876e3571965de64615d2012e63fae9c3ebbf8 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 15:44:43 +0200 Subject: [PATCH 106/126] ShellQuoteArguments: Fix quoting This accidentally used two quotes to start/end each quoted string. --- utils/utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/utils.go b/utils/utils.go index 94aa0dc902..6daac53a03 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1029,7 +1029,7 @@ func quote(word string, buf *bytes.Buffer) { return } - buf.WriteString("''") + buf.WriteString("'") for i := 0; i < len(word); i++ { b := word[i] @@ -1041,7 +1041,7 @@ func quote(word string, buf *bytes.Buffer) { } } - buf.WriteString("''") + buf.WriteString("'") } func ShellQuoteArguments(args []string) string { From b0a9147fd5f0197931b73e18163a12b04ad8e826 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 19:20:05 +0200 Subject: [PATCH 107/126] runtime test: Ensure all containers are unmounted at nuke() Otherwise we may leave around e.g. devmapper mounts --- container.go | 9 +++++++++ runtime_test.go | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/container.go b/container.go index e9fb4890b7..461004767d 100644 --- a/container.go +++ b/container.go @@ -1181,6 +1181,15 @@ func (container *Container) EnsureMounted() error { return container.Mount() } +func (container *Container) EnsureUnmounted() error { + if mounted, err := container.Mounted(); err != nil { + return err + } else if !mounted { + return nil + } + return container.Unmount() +} + func (container *Container) Mount() error { image, err := container.GetImage() if err != nil { diff --git a/runtime_test.go b/runtime_test.go index ccdf4d9563..684ca005cd 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -44,6 +44,10 @@ func nuke(runtime *Runtime) error { }(container) } wg.Wait() + + for _, container := range runtime.List() { + container.EnsureUnmounted() + } return os.RemoveAll(runtime.root) } From 6f57e8025a0a03da8052edc30157f7677f65f208 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 19:22:23 +0200 Subject: [PATCH 108/126] image: Unmount before removing device in error paths The device remove fails unless we unmount first --- image.go | 1 + 1 file changed, 1 insertion(+) diff --git a/image.go b/image.go index 16abf9eef0..f495e8e8fc 100644 --- a/image.go +++ b/image.go @@ -421,6 +421,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { err = image.applyLayer(layerPath(root), mountDir) if err != nil { + _ = devices.UnmountDevice(image.ID, mountDir) _ = devices.RemoveDevice(image.ID) return err } From 6ec5585501c6029ef10a474597503511d5074e10 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 20:38:06 +0200 Subject: [PATCH 109/126] Add DeviceSet.HasActivatedDevice() This lets you see if a device has been activated --- deviceset.go | 5 +++++ devmapper/deviceset_devmapper.go | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/deviceset.go b/deviceset.go index 5dd608361f..cf422acb3a 100644 --- a/deviceset.go +++ b/deviceset.go @@ -9,6 +9,7 @@ type DeviceSet interface { UnmountDevice(hash, path string) error HasDevice(hash string) bool HasInitializedDevice(hash string) bool + HasActivatedDevice(hash string) bool Shutdown() error } @@ -61,6 +62,10 @@ func (wrapper *DeviceSetWrapper) HasInitializedDevice(hash string) bool { return wrapper.wrapped.HasInitializedDevice(wrapper.wrap(hash)) } +func (wrapper *DeviceSetWrapper) HasActivatedDevice(hash string) bool { + return wrapper.wrapped.HasActivatedDevice(wrapper.wrap(hash)) +} + func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { wrapper := &DeviceSetWrapper{ wrapped: wrapped, diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go index 670d7621c4..bbf1fa6adc 100644 --- a/devmapper/deviceset_devmapper.go +++ b/devmapper/deviceset_devmapper.go @@ -851,6 +851,23 @@ func (devices *DeviceSetDM) HasInitializedDevice(hash string) bool { return info != nil && info.Initialized } +func (devices *DeviceSetDM) HasActivatedDevice(hash string) bool { + if err := devices.ensureInit(); err != nil { + return false + } + + info := devices.Devices[hash] + if info == nil { + return false + } + name := info.Name() + devinfo, _ := devices.getInfo(name) + if devinfo != nil && devinfo.Exists != 0 { + return true + } + return false +} + func (devices *DeviceSetDM) SetInitialized(hash string) error { if err := devices.ensureInit(); err != nil { return err From 071cc18b58408eaa71575e0233a056475196b199 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 19:23:35 +0200 Subject: [PATCH 110/126] Image.Changes: Deactivate image device after unmounting it There is no need to keep the image device around if we were the onces creating the device. --- image.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/image.go b/image.go index f495e8e8fc..ff8b836c39 100644 --- a/image.go +++ b/image.go @@ -617,6 +617,8 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er return nil, err } + wasActivated := devices.HasActivatedDevice(image.ID) + // We re-use rw for the temporary mount of the base image as its // not used by device-mapper otherwise err = devices.MountDevice(image.ID, rw) @@ -626,6 +628,9 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er changes, err := ChangesDirs(root, rw) _ = devices.UnmountDevice(image.ID, rw) + if !wasActivated { + _ = devices.DeactivateDevice(image.ID) + } if err != nil { return nil, err } From be6fef02544d9bc31321b2a21d039e261dc02bd8 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 19 Sep 2013 20:32:11 +0200 Subject: [PATCH 111/126] Tests: Clean up any old devmapper leftovers before starting tests --- devmapper/devmapper.go | 17 +++++++++++++++++ runtime_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/devmapper/devmapper.go b/devmapper/devmapper.go index 75742628a2..8458cb3ed9 100644 --- a/devmapper/devmapper.go +++ b/devmapper/devmapper.go @@ -347,3 +347,20 @@ func UdevWait(cookie uint32) error { func LogInitVerbose(level int) { C.dm_log_init_verbose(C.int(level)) } + +// Useful helper for cleanup +func RemoveDevice(name string) error { + task := TaskCreate(DeviceRemove) + if task == nil { + return fmt.Errorf("Can't create task of type DeviceRemove") + } + err := task.SetName(name) + if err != nil { + return fmt.Errorf("Can't set task name %s", name) + } + err = task.Run() + if err != nil { + return fmt.Errorf("Error running removeDevice") + } + return nil +} diff --git a/runtime_test.go b/runtime_test.go index 684ca005cd..bcbdf3f384 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -6,6 +6,7 @@ import ( "github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/devmapper" "io" + "io/ioutil" "log" "net" "os" @@ -84,6 +85,32 @@ func layerArchive(tarfile string) (io.Reader, error) { return f, nil } +// Remove any leftover device mapper devices from earlier runs of the unit tests +func cleanupDevMapper() { + infos, _ := ioutil.ReadDir("/dev/mapper") + if infos != nil { + hasPool := false + for _, info := range infos { + name := info.Name() + if strings.HasPrefix(name, "docker-unit-tests-devices-") { + if name == "docker-unit-tests-devices-pool" { + hasPool = true + } else { + if err := devmapper.RemoveDevice(name); err != nil { + panic(fmt.Errorf("Unable to remove existing device %s: %s", name, err)) + } + } + } + // We need to remove the pool last as the other devices block it + if hasPool { + if err := devmapper.RemoveDevice("docker-unit-tests-devices-pool"); err != nil { + panic(fmt.Errorf("Unable to remove existing device docker-unit-tests-devices-pool: %s", name, err)) + } + } + } + } +} + func init() { os.Setenv("TEST", "1") @@ -99,6 +126,8 @@ func init() { NetworkBridgeIface = unitTestNetworkBridge + cleanupDevMapper() + // Always start from a clean set of loopback mounts err := os.RemoveAll(unitTestStoreDevicesBase) if err != nil { From 0484b2c3254238a6c534f7d417c12c10b694f0d0 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 24 Sep 2013 14:16:11 +0200 Subject: [PATCH 112/126] RootIsShared: Fix root detection Column 4 is the mount position, column 3 will not always be "/" for the root. On one of my system its "/root". --- utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.go b/utils.go index 44573927a1..99600d1882 100644 --- a/utils.go +++ b/utils.go @@ -197,7 +197,7 @@ func RootIsShared() bool { if data, err := ioutil.ReadFile("/proc/self/mountinfo"); err == nil { for _, line := range strings.Split(string(data), "\n") { cols := strings.Split(line, " ") - if len(cols) >= 6 && cols[3] == "/" && cols[4] == "/" { + if len(cols) >= 6 && cols[4] == "/" { return strings.HasPrefix(cols[6], "shared") } } From aeb89ffbbac8c5625ad4dbba66938e2ef3f5c638 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 24 Sep 2013 17:20:58 +0000 Subject: [PATCH 113/126] add a -mount-method flag --- docker/docker.go | 7 ++++--- runtime.go | 14 +++++++++++--- runtime_test.go | 4 ++-- server.go | 4 ++-- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/docker/docker.go b/docker/docker.go index 4dcc174d5e..1f4dcc1d86 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -36,6 +36,7 @@ func main() { flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.") flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") flDns := flag.String("dns", "", "Set custom dns servers") + flMountMethod := flag.String("mount-method", "", "Set the mount method to use, default is auto. [aufs, devicemapper, filesystem]") flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)} flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") flag.Parse() @@ -65,7 +66,7 @@ func main() { flag.Usage() return } - if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil { + if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns, *flMountMethod); err != nil { log.Fatal(err) os.Exit(-1) } @@ -116,7 +117,7 @@ func removePidFile(pidfile string) { } } -func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error { +func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns, mountMethod string) error { if err := createPidFile(pidfile); err != nil { log.Fatal(err) } @@ -134,7 +135,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart if flDns != "" { dns = []string{flDns} } - server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns) + server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns, mountMethod) if err != nil { return err } diff --git a/runtime.go b/runtime.go index 10e937e21a..7b2455f304 100644 --- a/runtime.go +++ b/runtime.go @@ -519,8 +519,8 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a } // FIXME: harmonize with NewGraph() -func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string) (*Runtime, error) { - runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart) +func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string, mountMethod string) (*Runtime, error) { + runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart, mountMethod) if err != nil { return nil, err } @@ -538,7 +538,7 @@ func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns [ return runtime, nil } -func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) (*Runtime, error) { +func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool, mountMethod string) (*Runtime, error) { runtimeRepo := path.Join(root, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { @@ -578,6 +578,14 @@ func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) deviceSet: deviceSet, } + if mountMethod == "aufs" { + runtime.mountMethod = MountMethodAUFS + } else if mountMethod == "devicemapper" { + runtime.mountMethod = MountMethodDeviceMapper + } else if mountMethod == "filesystem" { + runtime.mountMethod = MountMethodFilesystem + } + if err := runtime.restore(); err != nil { return nil, err } diff --git a/runtime_test.go b/runtime_test.go index bcbdf3f384..b4be93fc1e 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -135,7 +135,7 @@ func init() { } // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false, ""); err != nil { panic(err) } else { globalRuntime = runtime @@ -504,7 +504,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false) + runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false, "") if err != nil { t.Fatal(err) } diff --git a/server.go b/server.go index f3081db81a..d468234ca2 100644 --- a/server.go +++ b/server.go @@ -1299,11 +1299,11 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er } -func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { +func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts, mountMethod string) (*Server, error) { if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } - runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns) + runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns, mountMethod) if err != nil { return nil, err } From 72a08a5458d5d8248dec431c82241a7b18b2657b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 26 Sep 2013 15:08:26 +0000 Subject: [PATCH 114/126] Revert "add a -mount-method flag" This reverts commit e52d756f40c9ccf8b37ca496cb72be057c909ed7. --- docker/docker.go | 7 +++---- runtime.go | 14 +++----------- runtime_test.go | 4 ++-- server.go | 4 ++-- utils_test.go | 2 +- 5 files changed, 11 insertions(+), 20 deletions(-) diff --git a/docker/docker.go b/docker/docker.go index 1f4dcc1d86..4dcc174d5e 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -36,7 +36,6 @@ func main() { flGraphPath := flag.String("g", "/var/lib/docker", "Path to graph storage base dir.") flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.") flDns := flag.String("dns", "", "Set custom dns servers") - flMountMethod := flag.String("mount-method", "", "Set the mount method to use, default is auto. [aufs, devicemapper, filesystem]") flHosts := docker.ListOpts{fmt.Sprintf("unix://%s", docker.DEFAULTUNIXSOCKET)} flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use") flag.Parse() @@ -66,7 +65,7 @@ func main() { flag.Usage() return } - if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns, *flMountMethod); err != nil { + if err := daemon(*pidfile, *flGraphPath, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil { log.Fatal(err) os.Exit(-1) } @@ -117,7 +116,7 @@ func removePidFile(pidfile string) { } } -func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns, mountMethod string) error { +func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error { if err := createPidFile(pidfile); err != nil { log.Fatal(err) } @@ -135,7 +134,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart if flDns != "" { dns = []string{flDns} } - server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns, mountMethod) + server, err := docker.NewServer(flGraphPath, devmapper.NewDeviceSetDM(flGraphPath), autoRestart, enableCors, dns) if err != nil { return err } diff --git a/runtime.go b/runtime.go index 7b2455f304..10e937e21a 100644 --- a/runtime.go +++ b/runtime.go @@ -519,8 +519,8 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a } // FIXME: harmonize with NewGraph() -func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string, mountMethod string) (*Runtime, error) { - runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart, mountMethod) +func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns []string) (*Runtime, error) { + runtime, err := NewRuntimeFromDirectory(flGraphPath, deviceSet, autoRestart) if err != nil { return nil, err } @@ -538,7 +538,7 @@ func NewRuntime(flGraphPath string, deviceSet DeviceSet, autoRestart bool, dns [ return runtime, nil } -func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool, mountMethod string) (*Runtime, error) { +func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool) (*Runtime, error) { runtimeRepo := path.Join(root, "containers") if err := os.MkdirAll(runtimeRepo, 0700); err != nil && !os.IsExist(err) { @@ -578,14 +578,6 @@ func NewRuntimeFromDirectory(root string, deviceSet DeviceSet, autoRestart bool, deviceSet: deviceSet, } - if mountMethod == "aufs" { - runtime.mountMethod = MountMethodAUFS - } else if mountMethod == "devicemapper" { - runtime.mountMethod = MountMethodDeviceMapper - } else if mountMethod == "filesystem" { - runtime.mountMethod = MountMethodFilesystem - } - if err := runtime.restore(); err != nil { return nil, err } diff --git a/runtime_test.go b/runtime_test.go index b4be93fc1e..bcbdf3f384 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -135,7 +135,7 @@ func init() { } // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false, ""); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { panic(err) } else { globalRuntime = runtime @@ -504,7 +504,7 @@ func TestRestore(t *testing.T) { // Here are are simulating a docker restart - that is, reloading all containers // from scratch - runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false, "") + runtime2, err := NewRuntimeFromDirectory(runtime1.root, runtime1.deviceSet, false) if err != nil { t.Fatal(err) } diff --git a/server.go b/server.go index d468234ca2..f3081db81a 100644 --- a/server.go +++ b/server.go @@ -1299,11 +1299,11 @@ func (srv *Server) ContainerCopy(name string, resource string, out io.Writer) er } -func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts, mountMethod string) (*Server, error) { +func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors bool, dns ListOpts) (*Server, error) { if runtime.GOARCH != "amd64" { log.Fatalf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH) } - runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns, mountMethod) + runtime, err := NewRuntime(flGraphPath, deviceSet, autoRestart, dns) if err != nil { return nil, err } diff --git a/utils_test.go b/utils_test.go index f5bdac3271..87f67ff0d7 100644 --- a/utils_test.go +++ b/utils_test.go @@ -43,7 +43,7 @@ func newTestRuntime() (*Runtime, error) { return nil, err } - runtime, err := NewRuntimeFromDirectory(root, NewDeviceSetWrapper (globalRuntime.deviceSet, filepath.Base(root)), false) + runtime, err := NewRuntimeFromDirectory(root, NewDeviceSetWrapper(globalRuntime.deviceSet, filepath.Base(root)), false) if err != nil { return nil, err } From 152302e379eaa5b2dfc27d901934da0a18b46beb Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 26 Sep 2013 15:40:13 +0000 Subject: [PATCH 115/126] go fmt and aufs support removed --- api_params.go | 14 +-- archive.go | 8 +- changes.go | 116 ++++--------------- container.go | 4 +- deviceset.go | 5 +- image.go | 290 +++++++++++++----------------------------------- mount.go | 29 ----- runtime.go | 34 +----- runtime_test.go | 17 ++- utils_test.go | 2 +- 10 files changed, 124 insertions(+), 395 deletions(-) diff --git a/api_params.go b/api_params.go index 6403bc6a26..5f1a338057 100644 --- a/api_params.go +++ b/api_params.go @@ -56,13 +56,13 @@ type APIContainers struct { func (self *APIContainers) ToLegacy() APIContainersOld { return APIContainersOld{ - ID: self.ID, - Image: self.Image, - Command: self.Command, - Created: self.Created, - Status: self.Status, - Ports: displayablePorts(self.Ports), - SizeRw: self.SizeRw, + ID: self.ID, + Image: self.Image, + Command: self.Command, + Created: self.Created, + Status: self.Status, + Ports: displayablePorts(self.Ports), + SizeRw: self.SizeRw, SizeRootFs: self.SizeRootFs, } } diff --git a/archive.go b/archive.go index f3f7b8c59e..75b6e7e1f1 100644 --- a/archive.go +++ b/archive.go @@ -84,13 +84,13 @@ func Tar(path string, compression Compression) (io.Reader, error) { } func escapeName(name string) string { - escaped := make([]byte,0) + escaped := make([]byte, 0) for i, c := range []byte(name) { if i == 0 && c == '/' { continue } // all printable chars except "-" which is 0x2d - if (0x20 <= c && c <= 0x7E) && c != 0x2d { + if (0x20 <= c && c <= 0x7E) && c != 0x2d { escaped = append(escaped, c) } else { escaped = append(escaped, fmt.Sprintf("\\%03o", c)...) @@ -102,7 +102,7 @@ func escapeName(name string) string { // Tar creates an archive from the directory at `path`, only including files whose relative // paths are included in `filter`. If `filter` is nil, then all files are included. func TarFilter(path string, compression Compression, filter []string, recursive bool, createFiles []string) (io.Reader, error) { - args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-",} + args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-"} if filter == nil { filter = []string{"."} } @@ -142,7 +142,7 @@ func TarFilter(path string, compression Compression, filter []string, recursive } } - return CmdStream(exec.Command(args[0], args[1:]...), &files, func () { + return CmdStream(exec.Command(args[0], args[1:]...), &files, func() { if tmpDir != "" { _ = os.RemoveAll(tmpDir) } diff --git a/changes.go b/changes.go index 49802d3170..77bef6fb22 100644 --- a/changes.go +++ b/changes.go @@ -34,82 +34,10 @@ func (change *Change) String() string { return fmt.Sprintf("%s %s", kind, change.Path) } -func ChangesAUFS(layers []string, rw string) ([]Change, error) { - var changes []Change - err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error { - if err != nil { - return err - } - - // Rebase path - path, err = filepath.Rel(rw, path) - if err != nil { - return err - } - path = filepath.Join("/", path) - - // Skip root - if path == "/" { - return nil - } - - // Skip AUFS metadata - if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched { - return err - } - - change := Change{ - Path: path, - } - - // Find out what kind of modification happened - file := filepath.Base(path) - // If there is a whiteout, then the file was removed - if strings.HasPrefix(file, ".wh.") { - originalFile := file[len(".wh."):] - change.Path = filepath.Join(filepath.Dir(path), originalFile) - change.Kind = ChangeDelete - } else { - // Otherwise, the file was added - change.Kind = ChangeAdd - - // ...Unless it already existed in a top layer, in which case, it's a modification - for _, layer := range layers { - stat, err := os.Stat(filepath.Join(layer, path)) - if err != nil && !os.IsNotExist(err) { - return err - } - if err == nil { - // The file existed in the top layer, so that's a modification - - // However, if it's a directory, maybe it wasn't actually modified. - // If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar - if stat.IsDir() && f.IsDir() { - if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() { - // Both directories are the same, don't record the change - return nil - } - } - change.Kind = ChangeModify - break - } - } - } - - // Record change - changes = append(changes, change) - return nil - }) - if err != nil && !os.IsNotExist(err) { - return nil, err - } - return changes, nil -} - type FileInfo struct { - parent *FileInfo - name string - stat syscall.Stat_t + parent *FileInfo + name string + stat syscall.Stat_t children map[string]*FileInfo } @@ -132,20 +60,20 @@ func (root *FileInfo) LookUp(path string) *FileInfo { return parent } -func (info *FileInfo)path() string { +func (info *FileInfo) path() string { if info.parent == nil { return "/" } return filepath.Join(info.parent.path(), info.name) } -func (info *FileInfo)unlink() { +func (info *FileInfo) unlink() { if info.parent != nil { delete(info.parent.children, info.name) } } -func (info *FileInfo)Remove(path string) bool { +func (info *FileInfo) Remove(path string) bool { child := info.LookUp(path) if child != nil { child.unlink() @@ -154,12 +82,11 @@ func (info *FileInfo)Remove(path string) bool { return false } -func (info *FileInfo)isDir() bool { +func (info *FileInfo) isDir() bool { return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR } - -func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { +func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { if oldInfo == nil { // add change := Change{ @@ -198,7 +125,7 @@ func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { oldStat.Gid != newStat.Gid || oldStat.Rdev != newStat.Rdev || // Don't look at size for dirs, its not a good measure of change - (oldStat.Size != newStat.Size && oldStat.Mode &syscall.S_IFDIR != syscall.S_IFDIR) || + (oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) || oldMtime.Sec != newMtime.Sec || oldMtime.Usec != newMtime.Usec { change := Change{ @@ -223,10 +150,9 @@ func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) { *changes = append(*changes, change) } - } -func (info *FileInfo)Changes(oldInfo *FileInfo) []Change { +func (info *FileInfo) Changes(oldInfo *FileInfo) []Change { var changes []Change info.addChanges(oldInfo, &changes) @@ -234,10 +160,9 @@ func (info *FileInfo)Changes(oldInfo *FileInfo) []Change { return changes } - func newRootFileInfo() *FileInfo { - root := &FileInfo { - name: "/", + root := &FileInfo{ + name: "/", children: make(map[string]*FileInfo), } return root @@ -299,11 +224,11 @@ func applyLayer(root *FileInfo, layer string) error { return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) } - info := &FileInfo { - name: filepath.Base(relPath), + info := &FileInfo{ + name: filepath.Base(relPath), children: make(map[string]*FileInfo), - parent: parent, - stat: layerStat, + parent: parent, + stat: layerStat, } parent.children[info.name] = info @@ -314,7 +239,6 @@ func applyLayer(root *FileInfo, layer string) error { return err } - func collectFileInfo(sourceDir string) (*FileInfo, error) { root := newRootFileInfo() @@ -339,10 +263,10 @@ func collectFileInfo(sourceDir string) (*FileInfo, error) { return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath) } - info := &FileInfo { - name: filepath.Base(relPath), + info := &FileInfo{ + name: filepath.Base(relPath), children: make(map[string]*FileInfo), - parent: parent, + parent: parent, } if err := syscall.Lstat(path, &info.stat); err != nil { @@ -365,7 +289,7 @@ func ChangesLayers(newDir string, layers []string) ([]Change, error) { return nil, err } oldRoot := newRootFileInfo() - for i := len(layers)-1; i >= 0; i-- { + for i := len(layers) - 1; i >= 0; i-- { layer := layers[i] if err = applyLayer(oldRoot, layer); err != nil { return nil, err diff --git a/container.go b/container.go index 461004767d..54fe6dcb7f 100644 --- a/container.go +++ b/container.go @@ -804,10 +804,10 @@ func (container *Container) Start(hostConfig *HostConfig) error { // without exec in go we have to do this horrible shell hack... shellString := "mount --make-rprivate /; exec " + - utils.ShellQuoteArguments(params) + utils.ShellQuoteArguments(params) params = []string{ - "unshare", "-m", "--", "/bin/sh", "-c", shellString, + "unshare", "-m", "--", "/bin/sh", "-c", shellString, } } diff --git a/deviceset.go b/deviceset.go index cf422acb3a..8e619ca248 100644 --- a/deviceset.go +++ b/deviceset.go @@ -15,7 +15,7 @@ type DeviceSet interface { type DeviceSetWrapper struct { wrapped DeviceSet - prefix string + prefix string } func (wrapper *DeviceSetWrapper) wrap(hash string) string { @@ -25,7 +25,6 @@ func (wrapper *DeviceSetWrapper) wrap(hash string) string { return hash } - func (wrapper *DeviceSetWrapper) AddDevice(hash, baseHash string) error { return wrapper.wrapped.AddDevice(wrapper.wrap(hash), wrapper.wrap(baseHash)) } @@ -69,7 +68,7 @@ func (wrapper *DeviceSetWrapper) HasActivatedDevice(hash string) bool { func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { wrapper := &DeviceSetWrapper{ wrapped: wrapped, - prefix: prefix, + prefix: prefix, } return wrapper } diff --git a/image.go b/image.go index ff8b836c39..2187605d51 100644 --- a/image.go +++ b/image.go @@ -8,9 +8,7 @@ import ( "github.com/dotcloud/docker/utils" "io" "io/ioutil" - "log" "os" - "os/exec" "path" "path/filepath" "strconv" @@ -141,31 +139,6 @@ func mountPath(root string) string { return path.Join(root, "mount") } -func MountAUFS(ro []string, rw string, target string) error { - // FIXME: Now mount the layers - rwBranch := fmt.Sprintf("%v=rw", rw) - roBranches := "" - for _, layer := range ro { - roBranches += fmt.Sprintf("%v=ro+wh:", layer) - } - branches := fmt.Sprintf("br:%v:%v", rwBranch, roBranches) - - branches += ",xino=/dev/shm/aufs.xino" - - //if error, try to load aufs kernel module - if err := mount("none", target, "aufs", 0, branches); err != nil { - log.Printf("Kernel does not support AUFS, trying to load the AUFS module with modprobe...") - if err := exec.Command("modprobe", "aufs").Run(); err != nil { - return fmt.Errorf("Unable to load the AUFS module") - } - log.Printf("...module loaded.") - if err := mount("none", target, "aufs", 0, branches); err != nil { - return fmt.Errorf("Unable to mount using aufs") - } - } - return nil -} - // TarLayer returns a tar archive of the image's filesystem layer. func (image *Image) TarLayer(compression Compression) (Archive, error) { layerPath, err := image.layer() @@ -315,7 +288,7 @@ func (image *Image) applyLayer(layer, target string) error { syscall.NsecToTimeval(srcStat.Mtim.Nano()), } - u := TimeUpdate { + u := TimeUpdate{ path: targetPath, time: ts, } @@ -335,7 +308,7 @@ func (image *Image) applyLayer(layer, target string) error { update := updateTimes[i] O_PATH := 010000000 // Not in syscall yet - fd, err := syscall.Open(update.path, syscall.O_RDWR | O_PATH | syscall.O_NOFOLLOW, 0600) + fd, err := syscall.Open(update.path, syscall.O_RDWR|O_PATH|syscall.O_NOFOLLOW, 0600) if err == syscall.EISDIR || err == syscall.ELOOP { // O_PATH not supported, use Utimes except on symlinks where Utimes doesn't work if err != syscall.ELOOP { @@ -411,7 +384,6 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { return err } - err = ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600) if err != nil { _ = devices.UnmountDevice(image.ID, mountDir) @@ -461,25 +433,7 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { } func (image *Image) Mounted(runtime *Runtime, root, rw string) (bool, error) { - method := runtime.GetMountMethod() - if method == MountMethodFilesystem { - if _, err := os.Stat(rw); err != nil { - if os.IsNotExist(err) { - err = nil - } - return false, err - } - mountedPath := path.Join(rw, ".fs-mounted") - if _, err := os.Stat(mountedPath); err != nil { - if os.IsNotExist(err) { - err = nil - } - return false, err - } - return true, nil - } else { - return Mounted(root) - } + return Mounted(root) } func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { @@ -492,195 +446,107 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { return err } - switch runtime.GetMountMethod() { - case MountMethodNone: - return fmt.Errorf("No supported Mount implementation") + devices, err := runtime.GetDeviceSet() + if err != nil { + return err + } + err = image.ensureImageDevice(devices) + if err != nil { + return err + } - case MountMethodAUFS: - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return err - } - layers, err := image.layers() + createdDevice := false + if !devices.HasDevice(id) { + utils.Debugf("Creating device %s for container based on image %s", id, image.ID) + err = devices.AddDevice(id, image.ID) if err != nil { return err } - if err := MountAUFS(layers, rw, root); err != nil { - return err - } + createdDevice = true + } - case MountMethodDeviceMapper: - devices, err := runtime.GetDeviceSet() + utils.Debugf("Mounting container %s at %s for container", id, root) + err = devices.MountDevice(id, root) + if err != nil { + return err + } + + if createdDevice { + err = ioutil.WriteFile(path.Join(root, ".docker-id"), []byte(id), 0600) if err != nil { + _ = devices.RemoveDevice(image.ID) return err } - err = image.ensureImageDevice(devices) - if err != nil { - return err - } - - createdDevice := false - if !devices.HasDevice(id) { - utils.Debugf("Creating device %s for container based on image %s", id, image.ID) - err = devices.AddDevice(id, image.ID) - if err != nil { - return err - } - createdDevice = true - } - - utils.Debugf("Mounting container %s at %s for container", id, root) - err = devices.MountDevice(id, root) - if err != nil { - return err - } - - if createdDevice { - err = ioutil.WriteFile(path.Join(root, ".docker-id"), []byte(id), 0600) - if err != nil { - _ = devices.RemoveDevice(image.ID) - return err - } - } - - case MountMethodFilesystem: - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return err - } - - layers, err := image.layers() - if err != nil { - return err - } - - for i := len(layers)-1; i >= 0; i-- { - layer := layers[i] - if err = image.applyLayer(layer, root); err != nil { - return err - } - } - - mountedPath := path.Join(rw, ".fs-mounted") - fo, err := os.Create(mountedPath) - if err != nil { - return err - } - fo.Close() } return nil } func (image *Image) Unmount(runtime *Runtime, root string, id string) error { - switch runtime.GetMountMethod() { - case MountMethodNone: - return fmt.Errorf("No supported Unmount implementation") - - case MountMethodAUFS: - return Unmount(root) - - case MountMethodDeviceMapper: - // Try to deactivate the device as generally there is no use for it anymore - devices, err := runtime.GetDeviceSet() - if err != nil { - return err; - } - - err = devices.UnmountDevice(id, root) - if err != nil { - return err - } - - return devices.DeactivateDevice(id) - - case MountMethodFilesystem: - return nil + // Try to deactivate the device as generally there is no use for it anymore + devices, err := runtime.GetDeviceSet() + if err != nil { + return err } - return nil + err = devices.UnmountDevice(id, root) + if err != nil { + return err + } + + return devices.DeactivateDevice(id) } func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, error) { - switch runtime.GetMountMethod() { - case MountMethodAUFS: - layers, err := image.layers() - if err != nil { - return nil, err - } - return ChangesAUFS(layers, rw) - - case MountMethodDeviceMapper: - devices, err := runtime.GetDeviceSet() - if err != nil { - return nil, err - } - - if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { - return nil, err - } - - wasActivated := devices.HasActivatedDevice(image.ID) - - // We re-use rw for the temporary mount of the base image as its - // not used by device-mapper otherwise - err = devices.MountDevice(image.ID, rw) - if err != nil { - return nil, err - } - - changes, err := ChangesDirs(root, rw) - _ = devices.UnmountDevice(image.ID, rw) - if !wasActivated { - _ = devices.DeactivateDevice(image.ID) - } - if err != nil { - return nil, err - } - return changes, nil - - case MountMethodFilesystem: - layers, err := image.layers() - if err != nil { - return nil, err - } - changes, err := ChangesLayers(root, layers) - if err != nil { - return nil, err - } - return changes, nil + devices, err := runtime.GetDeviceSet() + if err != nil { + return nil, err } - return nil, fmt.Errorf("No supported Changes implementation") + if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) { + return nil, err + } + + wasActivated := devices.HasActivatedDevice(image.ID) + + // We re-use rw for the temporary mount of the base image as its + // not used by device-mapper otherwise + err = devices.MountDevice(image.ID, rw) + if err != nil { + return nil, err + } + + changes, err := ChangesDirs(root, rw) + _ = devices.UnmountDevice(image.ID, rw) + if !wasActivated { + _ = devices.DeactivateDevice(image.ID) + } + if err != nil { + return nil, err + } + return changes, nil } func (image *Image) ExportChanges(runtime *Runtime, root, rw, id string) (Archive, error) { - switch runtime.GetMountMethod() { - case MountMethodAUFS: - return Tar(rw, Uncompressed) - - case MountMethodFilesystem, MountMethodDeviceMapper: - changes, err := image.Changes(runtime, root, rw, id) - if err != nil { - return nil, err - } - - files := make([]string, 0) - deletions := make([]string, 0) - for _, change := range changes { - if change.Kind == ChangeModify || change.Kind == ChangeAdd { - files = append(files, change.Path) - } - if change.Kind == ChangeDelete { - base := filepath.Base(change.Path) - dir := filepath.Dir(change.Path) - deletions = append(deletions, filepath.Join(dir, ".wh."+base)) - } - } - - return TarFilter(root, Uncompressed, files, false, deletions) + changes, err := image.Changes(runtime, root, rw, id) + if err != nil { + return nil, err } - return nil, fmt.Errorf("No supported Changes implementation") -} + files := make([]string, 0) + deletions := make([]string, 0) + for _, change := range changes { + if change.Kind == ChangeModify || change.Kind == ChangeAdd { + files = append(files, change.Path) + } + if change.Kind == ChangeDelete { + base := filepath.Base(change.Path) + dir := filepath.Dir(change.Path) + deletions = append(deletions, filepath.Join(dir, ".wh."+base)) + } + } + return TarFilter(root, Uncompressed, files, false, deletions) +} func (image *Image) ShortID() string { return utils.TruncateID(image.ID) diff --git a/mount.go b/mount.go index 541c29c13a..3e2a21df50 100644 --- a/mount.go +++ b/mount.go @@ -1,40 +1,11 @@ package docker import ( - "fmt" - "github.com/dotcloud/docker/utils" "os" - "os/exec" "path/filepath" "syscall" - "time" ) -func Unmount(target string) error { - if err := exec.Command("auplink", target, "flush").Run(); err != nil { - utils.Debugf("[warning]: couldn't run auplink before unmount: %s", err) - } - if err := syscall.Unmount(target, 0); err != nil { - return err - } - // Even though we just unmounted the filesystem, AUFS will prevent deleting the mntpoint - // for some time. We'll just keep retrying until it succeeds. - for retries := 0; retries < 1000; retries++ { - err := os.Remove(target) - if err == nil { - // rm mntpoint succeeded - return nil - } - if os.IsNotExist(err) { - // mntpoint doesn't exist anymore. Success. - return nil - } - // fmt.Printf("(%v) Remove %v returned: %v\n", retries, target, err) - time.Sleep(10 * time.Millisecond) - } - return fmt.Errorf("Umount: Failed to umount %v", target) -} - func Mounted(mountpoint string) (bool, error) { mntpoint, err := os.Stat(mountpoint) if err != nil { diff --git a/runtime.go b/runtime.go index 10e937e21a..b88f6d98dd 100644 --- a/runtime.go +++ b/runtime.go @@ -17,14 +17,6 @@ import ( ) var defaultDns = []string{"8.8.8.8", "8.8.4.4"} -type MountMethod int - -const ( - MountMethodNone MountMethod = iota - MountMethodAUFS - MountMethodDeviceMapper - MountMethodFilesystem -) type Capabilities struct { MemoryLimit bool @@ -47,7 +39,6 @@ type Runtime struct { srv *Server Dns []string deviceSet DeviceSet - mountMethod MountMethod } var sysInitPath string @@ -109,27 +100,6 @@ func hasFilesystemSupport(fstype string) bool { return false } -func (runtime *Runtime) GetMountMethod() MountMethod { - if runtime.mountMethod == MountMethodNone { - // Try to automatically pick a method - if hasFilesystemSupport("aufs") { - utils.Debugf("Using AUFS backend.") - runtime.mountMethod = MountMethodAUFS - } else { - _ = exec.Command("modprobe", "aufs").Run() - if hasFilesystemSupport("aufs") { - utils.Debugf("Using AUFS backend.") - runtime.mountMethod = MountMethodAUFS - } else { - utils.Debugf("Using device-mapper backend.") - runtime.mountMethod = MountMethodDeviceMapper - } - } - } - - return runtime.mountMethod -} - func (runtime *Runtime) GetDeviceSet() (DeviceSet, error) { if runtime.deviceSet == nil { return nil, fmt.Errorf("No device set available") @@ -289,7 +259,7 @@ func (runtime *Runtime) Destroy(container *Container) error { if err := os.RemoveAll(container.root); err != nil { return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err) } - if runtime.GetMountMethod() == MountMethodDeviceMapper && runtime.deviceSet.HasDevice(container.ID) { + if runtime.deviceSet.HasDevice(container.ID) { if err := runtime.deviceSet.RemoveDevice(container.ID); err != nil { return fmt.Errorf("Unable to remove device for %v: %v", container.ID, err) } @@ -302,7 +272,7 @@ func (runtime *Runtime) DeleteImage(id string) error { if err != nil { return err } - if runtime.GetMountMethod() == MountMethodDeviceMapper && runtime.deviceSet.HasDevice(id) { + if runtime.deviceSet.HasDevice(id) { if err := runtime.deviceSet.RemoveDevice(id); err != nil { return fmt.Errorf("Unable to remove device for %v: %v", id, err) } diff --git a/runtime_test.go b/runtime_test.go index bcbdf3f384..0a9b95a411 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -3,8 +3,8 @@ package docker import ( "bytes" "fmt" - "github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/devmapper" + "github.com/dotcloud/docker/utils" "io" "io/ioutil" "log" @@ -20,13 +20,13 @@ import ( ) const ( - unitTestImageName = "docker-test-image" - unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 - unitTestNetworkBridge = "testdockbr0" - unitTestStoreBase = "/var/lib/docker/unit-tests" - unitTestStoreDevicesBase = "/var/lib/docker/unit-tests-devices" - testDaemonAddr = "127.0.0.1:4270" - testDaemonProto = "tcp" + unitTestImageName = "docker-test-image" + unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 + unitTestNetworkBridge = "testdockbr0" + unitTestStoreBase = "/var/lib/docker/unit-tests" + unitTestStoreDevicesBase = "/var/lib/docker/unit-tests-devices" + testDaemonAddr = "127.0.0.1:4270" + testDaemonProto = "tcp" ) var ( @@ -75,7 +75,6 @@ func cleanupLast(runtime *Runtime) error { return nil } - func layerArchive(tarfile string) (io.Reader, error) { // FIXME: need to close f somewhere f, err := os.Open(tarfile) diff --git a/utils_test.go b/utils_test.go index 87f67ff0d7..f458b1da91 100644 --- a/utils_test.go +++ b/utils_test.go @@ -2,11 +2,11 @@ package docker import ( "github.com/dotcloud/docker/utils" - "path/filepath" "io" "io/ioutil" "os" "path" + "path/filepath" "strings" "testing" ) From 55e1782d6623b59af3f1aea1eb9646a36bbb5579 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 26 Sep 2013 21:12:12 +0200 Subject: [PATCH 116/126] Image: Fix time setting for old kernels This is a better fix for futimes() on kernels not supporting O_PATH. The previous fix broke when copying a device, as it tried to open it and got and error. --- image.go | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/image.go b/image.go index 2187605d51..06036e1e62 100644 --- a/image.go +++ b/image.go @@ -151,6 +151,7 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) { type TimeUpdate struct { path string time []syscall.Timeval + mode uint32 } func (image *Image) applyLayer(layer, target string) error { @@ -291,6 +292,7 @@ func (image *Image) applyLayer(layer, target string) error { u := TimeUpdate{ path: targetPath, time: ts, + mode: srcStat.Mode, } // Delay time updates until all other changes done, or it is @@ -308,21 +310,24 @@ func (image *Image) applyLayer(layer, target string) error { update := updateTimes[i] O_PATH := 010000000 // Not in syscall yet - fd, err := syscall.Open(update.path, syscall.O_RDWR|O_PATH|syscall.O_NOFOLLOW, 0600) - if err == syscall.EISDIR || err == syscall.ELOOP { - // O_PATH not supported, use Utimes except on symlinks where Utimes doesn't work - if err != syscall.ELOOP { - err = syscall.Utimes(update.path, update.time) - if err != nil { - return err - } + var err error = nil + if update.mode&syscall.S_IFLNK == syscall.S_IFLNK { + // Update time on the symlink via O_PATH + futimes(), if supported by the kernel + + fd, err := syscall.Open(update.path, syscall.O_RDWR|O_PATH|syscall.O_NOFOLLOW, 0600) + if err == syscall.EISDIR || err == syscall.ELOOP { + // O_PATH not supported by kernel, nothing to do, ignore + } else if err != nil { + return err + } else { + syscall.Futimes(fd, update.time) + _ = syscall.Close(fd) } } else { + err = syscall.Utimes(update.path, update.time) if err != nil { return err } - syscall.Futimes(fd, update.time) - _ = syscall.Close(fd) } } From 55189307d0ec6992f76572fae675bd371cc36cff Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 3 Oct 2013 15:47:38 +0000 Subject: [PATCH 117/126] disable: don't create device nodes manually if udev is not availabile as we don't have it in dind --- devmapper/deviceset_devmapper.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go index bbf1fa6adc..58da0db97d 100644 --- a/devmapper/deviceset_devmapper.go +++ b/devmapper/deviceset_devmapper.go @@ -204,7 +204,7 @@ func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) } var cookie uint32 = 0 - err = task.SetCookie(&cookie, 32) + err = task.SetCookie(&cookie, 0) if err != nil { return fmt.Errorf("Can't set cookie") } @@ -238,7 +238,7 @@ func (devices *DeviceSetDM) resumeDevice(info *DevInfo) error { } var cookie uint32 = 0 - err = task.SetCookie(&cookie, 32) + err = task.SetCookie(&cookie, 0) if err != nil { return fmt.Errorf("Can't set cookie") } @@ -374,7 +374,7 @@ func (devices *DeviceSetDM) activateDevice(info *DevInfo) error { } var cookie uint32 = 0 - err = task.SetCookie(&cookie, 32) + err = task.SetCookie(&cookie, 0) if err != nil { return fmt.Errorf("Can't set cookie") } From 06d0843a61843e6b16a56d518e21032a5652098b Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 30 Sep 2013 21:14:54 -0600 Subject: [PATCH 118/126] Update Dockerfile and hack to support compiling device-mapper code statically (using go1.2rc1) --- Dockerfile | 13 +++++++++++-- hack/PACKAGERS.md | 27 ++++++++++++--------------- hack/make.sh | 4 ++-- hack/make/binary | 2 +- hack/make/test | 4 +++- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index abb8a02828..1c29ed7615 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,13 +36,22 @@ run apt-get install -y -q mercurial run apt-get install -y -q build-essential # Install Go from source (for eventual cross-compiling) -env CGO_ENABLED 0 -run curl -s https://go.googlecode.com/files/go1.1.2.src.tar.gz | tar -v -C / -xz && mv /go /goroot +run curl -s https://go.googlecode.com/files/go1.2rc1.src.tar.gz | tar -v -C / -xz && mv /go /goroot run cd /goroot/src && ./make.bash env GOROOT /goroot env PATH $PATH:/goroot/bin env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor +# Create Go cache with tag netgo (for static compilation of Go while preserving CGO support) +run go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std + +# Get lvm2 source for compiling statically +run git clone git://git.fedorahosted.org/git/lvm2.git /lvm2 +run cd /lvm2 && git checkout v2_02_102 +# can't use git clone -b because it's not supported by git versions before 1.7.10 +run cd /lvm2 && ./configure --enable-static_link && make && make install_device-mapper +# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags + # Ubuntu stuff run apt-get install -y -q ruby1.9.3 rubygems libffi-dev run gem install --no-rdoc --no-ri fpm diff --git a/hack/PACKAGERS.md b/hack/PACKAGERS.md index 776ed47472..44f71aa94f 100644 --- a/hack/PACKAGERS.md +++ b/hack/PACKAGERS.md @@ -32,14 +32,14 @@ the process. ## System build dependencies -To build docker, you will need the following system dependencies +To build docker, you will need the following system dependencies: * An amd64 machine * A recent version of git and mercurial -* Go version 1.1.2 +* Go version 1.2rc1 +* A copy of libdevmapper.a (statically compiled), and associated headers * A clean checkout of the source must be added to a valid Go [workspace](http://golang.org/doc/code.html#Workspaces) -under the path *src/github.com/dotcloud/docker*. See - +under the path *src/github.com/dotcloud/docker*. ## Go dependencies @@ -55,15 +55,13 @@ NOTE: if you''re not able to package the exact version (to the exact commit) of please get in touch so we can remediate! Who knows what discrepancies can be caused by even the slightest deviation. We promise to do our best to make everybody happy. +## Disabling CGO for the net package -## Disabling CGO - -Make sure to disable CGO on your system, and then recompile the standard library on the build -machine: +Make sure to disable CGO on your system for the net package using `-tags netgo`, +and then recompile the standard library on the build machine: ```bash -export CGO_ENABLED=0 -cd /tmp && echo 'package main' > t.go && go test -a -i -v +go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std ``` ## Building Docker @@ -71,7 +69,7 @@ cd /tmp && echo 'package main' > t.go && go test -a -i -v To build the docker binary, run the following command with the source checkout as the working directory: -``` +```bash ./hack/make.sh binary ``` @@ -80,9 +78,9 @@ This will create a static binary under *./bundles/$VERSION/binary/docker-$VERSIO You are encouraged to use ./hack/make.sh without modification. If you must absolutely write your own script (are you really, really sure you need to? make.sh is really not that complicated), -then please take care the respect the following: +then please take care to respect the following: -* In *./hack/make.sh*: $LDFLAGS, $VERSION and $GITCOMMIT +* In *./hack/make.sh*: $LDFLAGS, $BUILDFLAGS, $VERSION and $GITCOMMIT * In *./hack/make/binary*: the exact build command to run You may be tempted to tweak these settings. In particular, being a rigorous maintainer, you may want @@ -106,7 +104,6 @@ dependencies to be installed (see below). The test suite will also download a small test container, so you will need internet connectivity. - ## Runtime dependencies To run properly, docker needs the following software to be installed at runtime: @@ -115,7 +112,7 @@ To run properly, docker needs the following software to be installed at runtime: * iproute2 version 3.5 or later (build after 2012-05-21), and specifically the "ip" utility. * iptables version 1.4 or later * The lxc utility scripts (http://lxc.sourceforge.net) version 0.8 or later. -* Git version 1.7 or later +* Git version 1.7 or later ## Kernel dependencies diff --git a/hack/make.sh b/hack/make.sh index 98b62ea6ae..62863bb8d8 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -44,8 +44,8 @@ if [ -n "$(git status --porcelain)" ]; then fi # Use these flags when compiling the tests and final binary -LDFLAGS="-X main.GITCOMMIT $GITCOMMIT -X main.VERSION $VERSION -d -w" - +LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' +BUILDFLAGS='-tags netgo' bundle() { bundlescript=$1 diff --git a/hack/make/binary b/hack/make/binary index cff9f5c733..3301c4ad7f 100644 --- a/hack/make/binary +++ b/hack/make/binary @@ -2,6 +2,6 @@ DEST=$1 -if go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" ./docker; then +if go build -o $DEST/docker-$VERSION -ldflags "$LDFLAGS" $BUILDFLAGS ./docker; then echo "Created binary: $DEST/docker-$VERSION" fi diff --git a/hack/make/test b/hack/make/test index 9334c8b313..86e77c5913 100644 --- a/hack/make/test +++ b/hack/make/test @@ -1,3 +1,5 @@ +#!/bin/sh + DEST=$1 set -e @@ -9,7 +11,7 @@ bundle_test() { for test_dir in $(find_test_dirs); do ( set -x cd $test_dir - go test -v -ldflags "$LDFLAGS" + go test -v -ldflags "$LDFLAGS" $BUILDFLAGS ) done } 2>&1 | tee $DEST/test.log } From 8b2f4aab232880cb0d7faa18ae12adb33f483b2c Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 2 Oct 2013 20:18:15 -0700 Subject: [PATCH 119/126] Random improvments --- Dockerfile | 59 ++--- api.go | 1 + deviceset.go | 3 +- devmapper/deviceset_devmapper.go | 379 ++++++++++++++----------------- devmapper/devmapper.go | 128 +++++------ docker/docker.go | 2 +- hack/make/test | 2 +- image.go | 46 ++-- server.go | 14 +- utils/utils.go | 10 +- 10 files changed, 300 insertions(+), 344 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1c29ed7615..cf0a04b8ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,54 +24,57 @@ # docker-version 0.6.1 -from ubuntu:12.04 -maintainer Solomon Hykes +from ubuntu:12.10 +maintainer Solomon Hykes # Build dependencies -run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > /etc/apt/sources.list -run apt-get update -run apt-get install -y -q curl -run apt-get install -y -q git -run apt-get install -y -q mercurial -run apt-get install -y -q build-essential +run apt-get update +run apt-get install -y -q curl +run apt-get install -y -q git +run apt-get install -y -q mercurial +run apt-get install -y -q build-essential # Install Go from source (for eventual cross-compiling) -run curl -s https://go.googlecode.com/files/go1.2rc1.src.tar.gz | tar -v -C / -xz && mv /go /goroot -run cd /goroot/src && ./make.bash -env GOROOT /goroot -env PATH $PATH:/goroot/bin -env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor +run curl -s https://go.googlecode.com/files/go1.2rc1.src.tar.gz | tar -v -C / -xz && mv /go /goroot +run cd /goroot/src && ./make.bash +env GOROOT /goroot +env PATH $PATH:/goroot/bin +env GOPATH /go:/go/src/github.com/dotcloud/docker/vendor # Create Go cache with tag netgo (for static compilation of Go while preserving CGO support) -run go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std +run go install -ldflags '-w -linkmode external -extldflags "-static -Wl,--unresolved-symbols=ignore-in-shared-libs"' -tags netgo -a std # Get lvm2 source for compiling statically -run git clone git://git.fedorahosted.org/git/lvm2.git /lvm2 -run cd /lvm2 && git checkout v2_02_102 +run git clone git://git.fedorahosted.org/git/lvm2.git /lvm2 +run cd /lvm2 && git checkout v2_02_102 + # can't use git clone -b because it's not supported by git versions before 1.7.10 run cd /lvm2 && ./configure --enable-static_link && make && make install_device-mapper # see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags # Ubuntu stuff -run apt-get install -y -q ruby1.9.3 rubygems libffi-dev -run gem install --no-rdoc --no-ri fpm -run apt-get install -y -q reprepro dpkg-sig +run apt-get install -y -q ruby1.9.3 rubygems libffi-dev +run gem install --no-rdoc --no-ri fpm +run apt-get install -y -q reprepro dpkg-sig # Install s3cmd 1.0.1 (earlier versions don't support env variables in the config) -run apt-get install -y -q python-pip -run pip install s3cmd -run pip install python-magic -run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg +run apt-get install -y -q python-pip +run pip install s3cmd +run pip install python-magic +run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_KEY\n' > /.s3cfg # Runtime dependencies -run apt-get install -y -q iptables -run apt-get install -y -q lxc +run apt-get install -y -q iptables +run dpkg-divert --local --rename --add /sbin/initctl && \ + ln -s /bin/true /sbin/initctl && \ + apt-get install -y -q lxc -volume /var/lib/docker -workdir /go/src/github.com/dotcloud/docker +volume /var/lib/docker +workdir /go/src/github.com/dotcloud/docker # Wrap all commands in the "docker-in-docker" script to allow nested containers entrypoint ["hack/dind"] # Upload docker source -add . /go/src/github.com/dotcloud/docker +add . /go/src/github.com/dotcloud/docker + diff --git a/api.go b/api.go index bc0c436966..339ae68d48 100644 --- a/api.go +++ b/api.go @@ -620,6 +620,7 @@ func postContainersStart(srv *Server, version float64, w http.ResponseWriter, r } name := vars["name"] if err := srv.ContainerStart(name, hostConfig); err != nil { + utils.Debugf("error ContainerStart: %s", err) return err } w.WriteHeader(http.StatusNoContent) diff --git a/deviceset.go b/deviceset.go index 8e619ca248..7aca816f43 100644 --- a/deviceset.go +++ b/deviceset.go @@ -66,9 +66,8 @@ func (wrapper *DeviceSetWrapper) HasActivatedDevice(hash string) bool { } func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { - wrapper := &DeviceSetWrapper{ + return &DeviceSetWrapper{ wrapped: wrapped, prefix: prefix, } - return wrapper } diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go index 58da0db97d..bad3b7a017 100644 --- a/devmapper/deviceset_devmapper.go +++ b/devmapper/deviceset_devmapper.go @@ -1,12 +1,11 @@ package devmapper import ( - "github.com/dotcloud/docker/utils" "encoding/json" "fmt" + "github.com/dotcloud/docker/utils" "io" "io/ioutil" - "log" "os" "os/exec" "path" @@ -15,9 +14,11 @@ import ( "syscall" ) -const defaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024 -const defaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 -const defaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024 +const ( + defaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024 + defaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 + defaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024 +) type DevInfo struct { Hash string `json:"-"` @@ -33,14 +34,14 @@ type MetaData struct { } type DeviceSetDM struct { - initialized bool - root string - devicePrefix string MetaData + initialized bool + root string + devicePrefix string TransactionId uint64 NewTransactionId uint64 nextFreeDevice int - activeMounts map[string]int + activeMounts map[string]int } func getDevName(name string) string { @@ -68,7 +69,7 @@ func (devices *DeviceSetDM) jsonFile() string { } func (devices *DeviceSetDM) getPoolName() string { - return fmt.Sprintf("%s-pool", devices.devicePrefix) + return devices.devicePrefix + "-pool" } func (devices *DeviceSetDM) getPoolDevName() string { @@ -80,8 +81,7 @@ func (devices *DeviceSetDM) createTask(t TaskType, name string) (*Task, error) { if task == nil { return nil, fmt.Errorf("Can't create task of type %d", int(t)) } - err := task.SetName(name) - if err != nil { + if err := task.SetName(name); err != nil { return nil, fmt.Errorf("Can't set task name %s", name) } return task, nil @@ -92,60 +92,53 @@ func (devices *DeviceSetDM) getInfo(name string) (*Info, error) { if task == nil { return nil, err } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return nil, err } - info, err := task.GetInfo() - if err != nil { - return nil, err - } - return info, nil + return task.GetInfo() } func (devices *DeviceSetDM) getStatus(name string) (uint64, uint64, string, string, error) { task, err := devices.createTask(DeviceStatus, name) if task == nil { + utils.Debugf("getStatus: Error createTask: %s", err) return 0, 0, "", "", err } - err = task.Run() - if err != nil { + 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) } - var next uintptr = 0 - next, start, length, target_type, params := task.GetNextTarget(next) - + _, start, length, target_type, params := task.GetNextTarget(0) return start, length, target_type, params, nil } func (devices *DeviceSetDM) setTransactionId(oldId uint64, newId uint64) error { task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) if task == nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err = task.SetSector(0) - if err != nil { + if err := task.SetSector(0); err != nil { return fmt.Errorf("Can't set sector") } - message := fmt.Sprintf("set_transaction_id %d %d", oldId, newId) - err = task.SetMessage(message) - if err != nil { + if err := task.SetMessage(fmt.Sprintf("set_transaction_id %d %d", oldId, newId)); err != nil { return fmt.Errorf("Can't set message") } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return fmt.Errorf("Error running setTransactionId") } return nil @@ -167,18 +160,17 @@ func (devices *DeviceSetDM) ensureImage(name string, size int64) (string, error) return "", err } - _, err := os.Stat(filename) - if err != nil { + if _, err := os.Stat(filename); err != nil { if !os.IsNotExist(err) { return "", err } - log.Printf("Creating loopback file %s for device-manage use", filename) + utils.Debugf("Creating loopback file %s for device-manage use", filename) file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600) if err != nil { return "", err } - err = file.Truncate(size) - if err != nil { + + if err = file.Truncate(size); err != nil { return "", err } } @@ -189,6 +181,7 @@ func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) utils.Debugf("Activating device-mapper pool %s", devices.getPoolName()) task, err := devices.createTask(DeviceCreate, devices.getPoolName()) if task == nil { + utils.Debugf("\n--->Err: %s\n", err) return err } @@ -198,19 +191,16 @@ func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) } params := metadataFile.Name() + " " + dataFile.Name() + " 512 8192" - err = task.AddTarget(0, size/512, "thin-pool", params) - if err != nil { + if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil { return fmt.Errorf("Can't add target") } var cookie uint32 = 0 - err = task.SetCookie(&cookie, 0) - if err != nil { + if err := task.SetCookie(&cookie, 0); err != nil { return fmt.Errorf("Can't set cookie") } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return fmt.Errorf("Error running DeviceCreate") } @@ -222,10 +212,10 @@ func (devices *DeviceSetDM) createPool(dataFile *os.File, metadataFile *os.File) func (devices *DeviceSetDM) suspendDevice(info *DevInfo) error { task, err := devices.createTask(DeviceSuspend, info.Name()) if task == nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return fmt.Errorf("Error running DeviceSuspend") } return nil @@ -234,17 +224,16 @@ func (devices *DeviceSetDM) suspendDevice(info *DevInfo) error { func (devices *DeviceSetDM) resumeDevice(info *DevInfo) error { task, err := devices.createTask(DeviceResume, info.Name()) if task == nil { + utils.Debugf("\n--->Err: %s\n", err) return err } var cookie uint32 = 0 - err = task.SetCookie(&cookie, 0) - if err != nil { + if err := task.SetCookie(&cookie, 0); err != nil { return fmt.Errorf("Can't set cookie") } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return fmt.Errorf("Error running DeviceSuspend") } @@ -256,68 +245,60 @@ func (devices *DeviceSetDM) resumeDevice(info *DevInfo) error { func (devices *DeviceSetDM) createDevice(deviceId int) error { task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) if task == nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err = task.SetSector(0) - if err != nil { + if err := task.SetSector(0); err != nil { return fmt.Errorf("Can't set sector") } - message := fmt.Sprintf("create_thin %d", deviceId) - err = task.SetMessage(message) - if err != nil { + if err := task.SetMessage(fmt.Sprintf("create_thin %d", deviceId)); err != nil { return fmt.Errorf("Can't set message") } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return fmt.Errorf("Error running createDevice") } return nil } func (devices *DeviceSetDM) createSnapDevice(deviceId int, baseInfo *DevInfo) error { - doSuspend := false devinfo, _ := devices.getInfo(baseInfo.Name()) - if devinfo != nil && devinfo.Exists != 0 { - doSuspend = true - } + doSuspend := devinfo != nil && devinfo.Exists != 0 if doSuspend { - err := devices.suspendDevice(baseInfo) - if err != nil { + if err := devices.suspendDevice(baseInfo); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } } task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) if task == nil { - _ = devices.resumeDevice(baseInfo) + devices.resumeDevice(baseInfo) + utils.Debugf("\n--->Err: %s\n", err) return err } - err = task.SetSector(0) - if err != nil { - _ = devices.resumeDevice(baseInfo) + + if err := task.SetSector(0); err != nil { + devices.resumeDevice(baseInfo) return fmt.Errorf("Can't set sector") } - message := fmt.Sprintf("create_snap %d %d", deviceId, baseInfo.DeviceId) - err = task.SetMessage(message) - if err != nil { - _ = devices.resumeDevice(baseInfo) + if err := task.SetMessage(fmt.Sprintf("create_snap %d %d", deviceId, baseInfo.DeviceId)); err != nil { + devices.resumeDevice(baseInfo) return fmt.Errorf("Can't set message") } - err = task.Run() - if err != nil { - _ = devices.resumeDevice(baseInfo) + if err := task.Run(); err != nil { + devices.resumeDevice(baseInfo) return fmt.Errorf("Error running DeviceCreate") } if doSuspend { - err = devices.resumeDevice(baseInfo) - if err != nil { + if err := devices.resumeDevice(baseInfo); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } } @@ -328,22 +309,19 @@ func (devices *DeviceSetDM) createSnapDevice(deviceId int, baseInfo *DevInfo) er func (devices *DeviceSetDM) deleteDevice(deviceId int) error { task, err := devices.createTask(DeviceTargetMsg, devices.getPoolDevName()) if task == nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err = task.SetSector(0) - if err != nil { + if err := task.SetSector(0); err != nil { return fmt.Errorf("Can't set sector") } - message := fmt.Sprintf("delete %d", deviceId) - err = task.SetMessage(message) - if err != nil { + if err := task.SetMessage(fmt.Sprintf("delete %d", deviceId)); err != nil { return fmt.Errorf("Can't set message") } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return fmt.Errorf("Error running deleteDevice") } return nil @@ -352,10 +330,10 @@ func (devices *DeviceSetDM) deleteDevice(deviceId int) error { func (devices *DeviceSetDM) removeDevice(name string) error { task, err := devices.createTask(DeviceRemove, name) if task == nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err = task.Run() - if err != nil { + if err = task.Run(); err != nil { return fmt.Errorf("Error running removeDevice") } return nil @@ -364,23 +342,21 @@ func (devices *DeviceSetDM) removeDevice(name string) error { func (devices *DeviceSetDM) activateDevice(info *DevInfo) error { task, err := devices.createTask(DeviceCreate, info.Name()) if task == nil { + utils.Debugf("\n--->Err: %s\n", err) return err } params := fmt.Sprintf("%s %d", devices.getPoolDevName(), info.DeviceId) - err = task.AddTarget(0, info.Size/512, "thin", params) - if err != nil { + if err := task.AddTarget(0, info.Size/512, "thin", params); err != nil { return fmt.Errorf("Can't add target") } var cookie uint32 = 0 - err = task.SetCookie(&cookie, 0) - if err != nil { + if err := task.SetCookie(&cookie, 0); err != nil { return fmt.Errorf("Can't set cookie") } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return fmt.Errorf("Error running DeviceCreate") } @@ -404,61 +380,60 @@ func (devices *DeviceSetDM) allocateTransactionId() uint64 { func (devices *DeviceSetDM) saveMetadata() error { jsonData, err := json.Marshal(devices.MetaData) if err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } tmpFile, err := ioutil.TempFile(filepath.Dir(devices.jsonFile()), ".json") if err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } n, err := tmpFile.Write(jsonData) if err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } if n < len(jsonData) { - err = io.ErrShortWrite + return io.ErrShortWrite } - err = tmpFile.Sync() - if err != nil { + if err := tmpFile.Sync(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err = tmpFile.Close() - if err != nil { + if err := tmpFile.Close(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err = os.Rename(tmpFile.Name(), devices.jsonFile()) - if err != nil { + if err := os.Rename(tmpFile.Name(), devices.jsonFile()); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } if devices.NewTransactionId != devices.TransactionId { - err = devices.setTransactionId(devices.TransactionId, devices.NewTransactionId) - if err != nil { + if err = devices.setTransactionId(devices.TransactionId, devices.NewTransactionId); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } devices.TransactionId = devices.NewTransactionId } - return nil } func (devices *DeviceSetDM) registerDevice(id int, hash string, size uint64) (*DevInfo, error) { - transaction := devices.allocateTransactionId() - info := &DevInfo{ Hash: hash, DeviceId: id, Size: size, - TransactionId: transaction, + TransactionId: devices.allocateTransactionId(), Initialized: false, devices: devices, } devices.Devices[hash] = info - err := devices.saveMetadata() - if err != nil { + if err := devices.saveMetadata(); err != nil { // Try to remove unused device - devices.Devices[hash] = nil + delete(devices.Devices, hash) return nil, err } @@ -471,9 +446,7 @@ func (devices *DeviceSetDM) activateDeviceIfNeeded(hash string) error { return fmt.Errorf("Unknown device %s", hash) } - name := info.Name() - devinfo, _ := devices.getInfo(name) - if devinfo != nil && devinfo.Exists != 0 { + if devinfo, _ := devices.getInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 { return nil } @@ -483,13 +456,12 @@ func (devices *DeviceSetDM) activateDeviceIfNeeded(hash string) error { func (devices *DeviceSetDM) createFilesystem(info *DevInfo) error { devname := info.DevName() - err := exec.Command("mkfs.ext4", "-E", - "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run() + err := exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0,lazy_journal_init=0", devname).Run() if err != nil { - err = exec.Command("mkfs.ext4", "-E", - "discard,lazy_itable_init=0", devname).Run() + err = exec.Command("mkfs.ext4", "-E", "discard,lazy_itable_init=0", devname).Run() } if err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } return nil @@ -498,31 +470,29 @@ func (devices *DeviceSetDM) createFilesystem(info *DevInfo) error { func (devices *DeviceSetDM) loadMetaData() error { _, _, _, params, err := devices.getStatus(devices.getPoolName()) if err != nil { - return err - } - var currentTransaction uint64 - _, err = fmt.Sscanf(params, "%d", ¤tTransaction) - if err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - devices.TransactionId = currentTransaction + 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 && !os.IsNotExist(err) { + utils.Debugf("\n--->Err: %s\n", err) return err } - metadata := &MetaData{ - Devices: make(map[string]*DevInfo), - } + devices.MetaData.Devices = make(map[string]*DevInfo) if jsonData != nil { - if err := json.Unmarshal(jsonData, metadata); err != nil { + if err := json.Unmarshal(jsonData, &devices.MetaData); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } } - devices.MetaData = *metadata for hash, d := range devices.Devices { d.Hash = hash @@ -533,12 +503,11 @@ func (devices *DeviceSetDM) loadMetaData() error { } // If the transaction id is larger than the actual one we lost the device due to some crash - if d.TransactionId > currentTransaction { - log.Printf("Removing lost device %s with id %d", hash, d.TransactionId) + if d.TransactionId > devices.TransactionId { + utils.Debugf("Removing lost device %s with id %d", hash, d.TransactionId) delete(devices.Devices, hash) } } - return nil } @@ -549,45 +518,46 @@ func (devices *DeviceSetDM) setupBaseImage() error { } if oldInfo != nil && !oldInfo.Initialized { - log.Printf("Removing uninitialized base image") + utils.Debugf("Removing uninitialized base image") if err := devices.RemoveDevice(""); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } } - log.Printf("Initializing base device-manager snapshot") + utils.Debugf("Initializing base device-manager snapshot") id := devices.allocateDeviceId() // Create initial device - err := devices.createDevice(id) - if err != nil { + if err := devices.createDevice(id); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } info, err := devices.registerDevice(id, "", defaultBaseFsSize) if err != nil { _ = devices.deleteDevice(id) + utils.Debugf("\n--->Err: %s\n", err) return err } - log.Printf("Creating filesystem on base device-manager snapshot") + utils.Debugf("Creating filesystem on base device-manager snapshot") - err = devices.activateDeviceIfNeeded("") - if err != nil { + if err = devices.activateDeviceIfNeeded(""); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err = devices.createFilesystem(info) - if err != nil { + if err := devices.createFilesystem(info); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } info.Initialized = true - - err = devices.saveMetadata() - if err != nil { + if err = devices.saveMetadata(); err != nil { info.Initialized = false + utils.Debugf("\n--->Err: %s\n", err) return err } @@ -597,64 +567,67 @@ func (devices *DeviceSetDM) setupBaseImage() error { func (devices *DeviceSetDM) initDevmapper() error { info, err := devices.getInfo(devices.getPoolName()) if info == nil { + utils.Debugf("Error device getInfo: %s", err) return err } + utils.Debugf("initDevmapper(). Pool exists: %v", info.Exists) if info.Exists != 0 { /* Pool exists, assume everything is up */ - err = devices.loadMetaData() - if err != nil { + if err := devices.loadMetaData(); err != nil { + utils.Debugf("Error device loadMetaData: %s\n", err) return err } - err = devices.setupBaseImage() - if err != nil { + if err := devices.setupBaseImage(); err != nil { + utils.Debugf("Error device setupBaseImage: %s\n", err) return err } return nil } - createdLoopback := false - if !devices.hasImage("data") || !devices.hasImage("metadata") { - /* If we create the loopback mounts we also need to initialize the base fs */ - createdLoopback = true - } + /* If we create the loopback mounts we also need to initialize the base fs */ + createdLoopback := !devices.hasImage("data") || !devices.hasImage("metadata") 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 } 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() - err = devices.createPool(dataFile, metadataFile) - if err != nil { + if err := devices.createPool(dataFile, metadataFile); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } if !createdLoopback { - err = devices.loadMetaData() - if err != nil { + if err = devices.loadMetaData(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } } - err = devices.setupBaseImage() - if err != nil { + if err := devices.setupBaseImage(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } @@ -663,6 +636,7 @@ func (devices *DeviceSetDM) initDevmapper() error { func (devices *DeviceSetDM) AddDevice(hash, baseHash string) error { if err := devices.ensureInit(); err != nil { + utils.Debugf("Error init: %s\n", err) return err } @@ -672,19 +646,20 @@ func (devices *DeviceSetDM) AddDevice(hash, baseHash string) error { baseInfo := devices.Devices[baseHash] if baseInfo == nil { + utils.Debugf("Base Hash not found") return fmt.Errorf("Unknown base hash %s", baseHash) } deviceId := devices.allocateDeviceId() - err := devices.createSnapDevice(deviceId, baseInfo) - if err != nil { + if err := devices.createSnapDevice(deviceId, baseInfo); err != nil { + utils.Debugf("Error creating snap device: %s\n", err) return err } - _, err = devices.registerDevice(deviceId, hash, baseInfo.Size) - if err != nil { - _ = devices.deleteDevice(deviceId) + if _, err := devices.registerDevice(deviceId, hash, baseInfo.Size); err != nil { + devices.deleteDevice(deviceId) + utils.Debugf("Error registering device: %s\n", err) return err } return nil @@ -692,6 +667,7 @@ func (devices *DeviceSetDM) AddDevice(hash, baseHash string) error { func (devices *DeviceSetDM) RemoveDevice(hash string) error { if err := devices.ensureInit(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } @@ -702,31 +678,31 @@ func (devices *DeviceSetDM) RemoveDevice(hash string) error { devinfo, _ := devices.getInfo(info.Name()) if devinfo != nil && devinfo.Exists != 0 { - err := devices.removeDevice(info.Name()) - if err != nil { + if err := devices.removeDevice(info.Name()); err != nil { + utils.Debugf("Error removing device: %s\n", err) return err } } if info.Initialized { info.Initialized = false - err := devices.saveMetadata() - if err != nil { + if err := devices.saveMetadata(); err != nil { + utils.Debugf("Error saving meta data: %s\n", err) return err } } - err := devices.deleteDevice(info.DeviceId) - if err != nil { + if err := devices.deleteDevice(info.DeviceId); err != nil { + utils.Debugf("Error deleting device: %s\n", err) return err } - _ = devices.allocateTransactionId() + devices.allocateTransactionId() delete(devices.Devices, info.Hash) - err = devices.saveMetadata() - if err != nil { + if err := devices.saveMetadata(); err != nil { devices.Devices[info.Hash] = info + utils.Debugf("Error saving meta data: %s\n", err) return err } @@ -735,6 +711,7 @@ func (devices *DeviceSetDM) RemoveDevice(hash string) error { func (devices *DeviceSetDM) DeactivateDevice(hash string) error { if err := devices.ensureInit(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } @@ -745,11 +722,12 @@ func (devices *DeviceSetDM) DeactivateDevice(hash string) error { devinfo, err := devices.getInfo(info.Name()) if err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } if devinfo.Exists != 0 { - err := devices.removeDevice(info.Name()) - if err != nil { + if err := devices.removeDevice(info.Name()); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } } @@ -764,9 +742,8 @@ func (devices *DeviceSetDM) Shutdown() error { for path, count := range devices.activeMounts { for i := count; i > 0; i-- { - err := syscall.Unmount(path, 0) - if err != nil { - fmt.Printf("Shutdown unmounting %s, error: %s\n", path, err) + if err := syscall.Unmount(path, 0); err != nil { + utils.Debugf("Shutdown unmounting %s, error: %s\n", path, err) } } delete(devices.activeMounts, path) @@ -774,16 +751,14 @@ func (devices *DeviceSetDM) Shutdown() error { for _, d := range devices.Devices { if err := devices.DeactivateDevice(d.Hash); err != nil { - fmt.Printf("Shutdown deactivate %s , error: %s\n", d.Hash, err) + utils.Debugf("Shutdown deactivate %s , error: %s\n", d.Hash, err) } } - pool := devices.getPoolDevName() - devinfo, err := devices.getInfo(pool) - if err == nil && devinfo.Exists != 0 { + if devinfo, err := devices.getInfo(pool); err == nil && devinfo.Exists != 0 { if err := devices.removeDevice(pool); err != nil { - fmt.Printf("Shutdown deactivate %s , error: %s\n", pool, err) + utils.Debugf("Shutdown deactivate %s , error: %s\n", pool, err) } } @@ -792,21 +767,23 @@ func (devices *DeviceSetDM) Shutdown() error { func (devices *DeviceSetDM) MountDevice(hash, path string) error { if err := devices.ensureInit(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - err := devices.activateDeviceIfNeeded(hash) - if err != nil { + if err := devices.activateDeviceIfNeeded(hash); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } info := devices.Devices[hash] - err = syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "discard") + err := syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "discard") if err != nil && err == syscall.EINVAL { err = syscall.Mount(info.DevName(), path, "ext4", syscall.MS_MGC_VAL, "") } if err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } @@ -817,13 +794,12 @@ func (devices *DeviceSetDM) MountDevice(hash, path string) error { } func (devices *DeviceSetDM) UnmountDevice(hash, path string) error { - err := syscall.Unmount(path, 0) - if err != nil { + if err := syscall.Unmount(path, 0); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } - count := devices.activeMounts[path] - if count > 1 { + if count := devices.activeMounts[path]; count > 1 { devices.activeMounts[path] = count - 1 } else { delete(devices.activeMounts, path) @@ -832,14 +808,11 @@ func (devices *DeviceSetDM) UnmountDevice(hash, path string) error { return nil } - func (devices *DeviceSetDM) HasDevice(hash string) bool { if err := devices.ensureInit(); err != nil { return false } - - info := devices.Devices[hash] - return info != nil + return devices.Devices[hash] != nil } func (devices *DeviceSetDM) HasInitializedDevice(hash string) bool { @@ -860,16 +833,13 @@ func (devices *DeviceSetDM) HasActivatedDevice(hash string) bool { if info == nil { return false } - name := info.Name() - devinfo, _ := devices.getInfo(name) - if devinfo != nil && devinfo.Exists != 0 { - return true - } - return false + devinfo, _ := devices.getInfo(info.Name()) + return devinfo != nil && devinfo.Exists != 0 } func (devices *DeviceSetDM) SetInitialized(hash string) error { if err := devices.ensureInit(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } @@ -879,9 +849,9 @@ func (devices *DeviceSetDM) SetInitialized(hash string) error { } info.Initialized = true - err := devices.saveMetadata() - if err != nil { + if err := devices.saveMetadata(); err != nil { info.Initialized = false + utils.Debugf("\n--->Err: %s\n", err) return err } @@ -889,10 +859,11 @@ func (devices *DeviceSetDM) SetInitialized(hash string) error { } func (devices *DeviceSetDM) ensureInit() error { + utils.Debugf("ensureInit(). Initialized: %v", devices.initialized) if !devices.initialized { devices.initialized = true - err := devices.initDevmapper() - if err != nil { + if err := devices.initDevmapper(); err != nil { + utils.Debugf("\n--->Err: %s\n", err) return err } } @@ -907,13 +878,11 @@ func NewDeviceSetDM(root string) *DeviceSetDM { base = "docker-" + base } - devices := &DeviceSetDM{ - initialized: false, - root: root, + return &DeviceSetDM{ + initialized: false, + root: root, devicePrefix: base, + MetaData: MetaData{Devices: make(map[string]*DevInfo)}, + activeMounts: make(map[string]int), } - devices.Devices = make(map[string]*DevInfo) - devices.activeMounts = make(map[string]int) - - return devices } diff --git a/devmapper/devmapper.go b/devmapper/devmapper.go index 8458cb3ed9..3799fd2df9 100644 --- a/devmapper/devmapper.go +++ b/devmapper/devmapper.go @@ -108,30 +108,32 @@ get_block_size(int fd) */ import "C" -import "unsafe" -import "fmt" -import "runtime" -import "os" + +import ( + "fmt" + "github.com/dotcloud/docker/utils" + "os" + "runtime" + "unsafe" +) func SetDevDir(dir string) error { c_dir := C.CString(dir) defer C.free(unsafe.Pointer(c_dir)) - res := C.dm_set_dev_dir(c_dir) - if res != 1 { + + if res := C.dm_set_dev_dir(c_dir); res != 1 { + utils.Debugf("Error dm_set_dev_dir") return fmt.Errorf("dm_set_dev_dir failed") } return nil } func GetLibraryVersion() (string, error) { - buffer := (*C.char)(C.malloc(128)) - defer C.free(unsafe.Pointer(buffer)) - res := C.dm_get_library_version(buffer, 128) - if res != 1 { + buffer := make([]byte, 128) + if res := C.dm_get_library_version((*C.char)(unsafe.Pointer(&buffer)), 128); res != 1 { return "", fmt.Errorf("dm_get_library_version failed") - } else { - return C.GoString(buffer), nil } + return string(buffer), nil } type TaskType int @@ -183,18 +185,17 @@ func (t *Task) destroy() { } func TaskCreate(tasktype TaskType) *Task { - c_task := C.dm_task_create(C.int(int(tasktype))) + c_task := C.dm_task_create(C.int(tasktype)) if c_task == nil { return nil } - task := &Task{c_task} + task := &Task{unmanaged: c_task} runtime.SetFinalizer(task, (*Task).destroy) return task } func (t *Task) Run() error { - res := C.dm_task_run(t.unmanaged) - if res != 1 { + if res := C.dm_task_run(t.unmanaged); res != 1 { return fmt.Errorf("dm_task_run failed") } return nil @@ -204,8 +205,10 @@ func (t *Task) SetName(name string) error { c_name := C.CString(name) defer C.free(unsafe.Pointer(c_name)) - res := C.dm_task_set_name(t.unmanaged, c_name) - if res != 1 { + if res := C.dm_task_set_name(t.unmanaged, c_name); res != 1 { + if os.Getenv("DEBUG") != "" { + C.perror(C.CString(fmt.Sprintf("[debug] Error dm_task_set_name(%s, %#v)", name, t.unmanaged))) + } return fmt.Errorf("dm_task_set_name failed") } return nil @@ -215,26 +218,22 @@ func (t *Task) SetMessage(message string) error { c_message := C.CString(message) defer C.free(unsafe.Pointer(c_message)) - res := C.dm_task_set_message(t.unmanaged, c_message) - if res != 1 { + if res := C.dm_task_set_message(t.unmanaged, c_message); res != 1 { return fmt.Errorf("dm_task_set_message failed") } return nil } func (t *Task) SetSector(sector uint64) error { - res := C.dm_task_set_sector(t.unmanaged, C.uint64_t(sector)) - if res != 1 { + if res := C.dm_task_set_sector(t.unmanaged, C.uint64_t(sector)); res != 1 { return fmt.Errorf("dm_task_set_add_node failed") } return nil } func (t *Task) SetCookie(cookie *uint32, flags uint16) error { - var c_cookie C.uint32_t - c_cookie = C.uint32_t(*cookie) - res := C.dm_task_set_cookie(t.unmanaged, &c_cookie, C.uint16_t(flags)) - if res != 1 { + c_cookie := C.uint32_t(*cookie) + if res := C.dm_task_set_cookie(t.unmanaged, &c_cookie, C.uint16_t(flags)); res != 1 { return fmt.Errorf("dm_task_set_add_node failed") } *cookie = uint32(c_cookie) @@ -242,8 +241,7 @@ func (t *Task) SetCookie(cookie *uint32, flags uint16) error { } func (t *Task) SetRo() error { - res := C.dm_task_set_ro(t.unmanaged) - if res != 1 { + if res := C.dm_task_set_ro(t.unmanaged); res != 1 { return fmt.Errorf("dm_task_set_ro failed") } return nil @@ -256,8 +254,7 @@ func (t *Task) AddTarget(start uint64, size uint64, ttype string, params string) c_params := C.CString(params) defer C.free(unsafe.Pointer(c_params)) - res := C.dm_task_add_target(t.unmanaged, C.uint64_t(start), C.uint64_t(size), c_ttype, c_params) - if res != 1 { + if res := C.dm_task_add_target(t.unmanaged, C.uint64_t(start), C.uint64_t(size), c_ttype, c_params); res != 1 { return fmt.Errorf("dm_task_add_target failed") } return nil @@ -267,49 +264,39 @@ func (t *Task) GetDriverVersion() (string, error) { buffer := (*C.char)(C.malloc(128)) defer C.free(unsafe.Pointer(buffer)) - res := C.dm_task_get_driver_version(t.unmanaged, buffer, 128) - if res != 1 { + if res := C.dm_task_get_driver_version(t.unmanaged, buffer, 128); res != 1 { return "", fmt.Errorf("dm_task_get_driver_version") - } else { - return C.GoString(buffer), nil } + return C.GoString(buffer), nil } func (t *Task) GetInfo() (*Info, error) { c_info := C.struct_dm_info{} - res := C.dm_task_get_info(t.unmanaged, &c_info) - if res != 1 { + if res := C.dm_task_get_info(t.unmanaged, &c_info); res != 1 { return nil, fmt.Errorf("dm_task_get_driver_version") - } else { - info := &Info{} - info.Exists = int(c_info.exists) - info.Suspended = int(c_info.suspended) - info.LiveTable = int(c_info.live_table) - info.InactiveTable = int(c_info.inactive_table) - info.OpenCount = int32(c_info.open_count) - info.EventNr = uint32(c_info.event_nr) - info.Major = uint32(c_info.major) - info.Minor = uint32(c_info.minor) - info.ReadOnly = int(c_info.read_only) - info.TargetCount = int32(c_info.target_count) - - return info, nil } + return &Info{ + Exists: int(c_info.exists), + Suspended: int(c_info.suspended), + LiveTable: int(c_info.live_table), + InactiveTable: int(c_info.inactive_table), + OpenCount: int32(c_info.open_count), + EventNr: uint32(c_info.event_nr), + Major: uint32(c_info.major), + Minor: uint32(c_info.minor), + ReadOnly: int(c_info.read_only), + TargetCount: int32(c_info.target_count), + }, nil } func (t *Task) GetNextTarget(next uintptr) (uintptr, uint64, uint64, string, string) { - nextp := unsafe.Pointer(next) - var c_start C.uint64_t - var c_length C.uint64_t - var c_target_type *C.char - var c_params *C.char + var ( + c_start, c_length C.uint64_t + c_target_type, c_params *C.char + ) - nextp = C.dm_get_next_target(t.unmanaged, nextp, &c_start, &c_length, &c_target_type, &c_params) - - target_type := C.GoString(c_target_type) - params := C.GoString(c_params) - - return uintptr(nextp), uint64(c_start), uint64(c_length), target_type, params + nextp := C.dm_get_next_target(t.unmanaged, unsafe.Pointer(next), &c_start, &c_length, &c_target_type, &c_params) + return uintptr(nextp), uint64(c_start), uint64(c_length), C.GoString(c_target_type), C.GoString(c_params) } func AttachLoopDevice(filename string) (*os.File, error) { @@ -321,19 +308,16 @@ func AttachLoopDevice(filename string) (*os.File, error) { if res == nil { return nil, fmt.Errorf("error loopback mounting") } - file := os.NewFile(uintptr(fd), C.GoString(res)) - C.free(unsafe.Pointer(res)) - return file, nil + defer C.free(unsafe.Pointer(res)) + return os.NewFile(uintptr(fd), C.GoString(res)), nil } func GetBlockDeviceSize(file *os.File) (uint64, error) { - fd := file.Fd() - size := C.get_block_size(C.int(fd)) - if size == -1 { + if size := C.get_block_size(C.int(file.Fd())); size == -1 { return 0, fmt.Errorf("Can't get block size") + } else { + return uint64(size), nil } - return uint64(size), nil - } func UdevWait(cookie uint32) error { @@ -354,12 +338,10 @@ func RemoveDevice(name string) error { if task == nil { return fmt.Errorf("Can't create task of type DeviceRemove") } - err := task.SetName(name) - if err != nil { + if err := task.SetName(name); err != nil { return fmt.Errorf("Can't set task name %s", name) } - err = task.Run() - if err != nil { + if err := task.Run(); err != nil { return fmt.Errorf("Error running removeDevice") } return nil diff --git a/docker/docker.go b/docker/docker.go index 4dcc174d5e..3ecf02d326 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -123,7 +123,7 @@ func daemon(pidfile string, flGraphPath string, protoAddrs []string, autoRestart defer removePidFile(pidfile) c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, os.Kill, os.Signal(syscall.SIGTERM)) + signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM) go func() { sig := <-c log.Printf("Received signal '%v', exiting\n", sig) diff --git a/hack/make/test b/hack/make/test index 86e77c5913..1dadbdab8e 100644 --- a/hack/make/test +++ b/hack/make/test @@ -11,7 +11,7 @@ bundle_test() { for test_dir in $(find_test_dirs); do ( set -x cd $test_dir - go test -v -ldflags "$LDFLAGS" $BUILDFLAGS + DEBUG=1 go test -v -ldflags "$LDFLAGS" $BUILDFLAGS -run TestRunHostname ) done } 2>&1 | tee $DEST/test.log } diff --git a/image.go b/image.go index 06036e1e62..175bcfc6ae 100644 --- a/image.go +++ b/image.go @@ -378,28 +378,28 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { } utils.Debugf("Creating device-mapper device for image id %s", image.ID) - err = devices.AddDevice(image.ID, image.Parent) - if err != nil { + if err := devices.AddDevice(image.ID, image.Parent); err != nil { + utils.Debugf("Error add device: %s", err) return err } - err = devices.MountDevice(image.ID, mountDir) - if err != nil { - _ = devices.RemoveDevice(image.ID) + if err := devices.MountDevice(image.ID, mountDir); err != nil { + utils.Debugf("Error mounting device: %s", err) + devices.RemoveDevice(image.ID) return err } - err = ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600) - if err != nil { - _ = devices.UnmountDevice(image.ID, mountDir) - _ = devices.RemoveDevice(image.ID) + if err := ioutil.WriteFile(path.Join(mountDir, ".docker-id"), []byte(image.ID), 0600); err != nil { + utils.Debugf("Error writing file: %s", err) + devices.UnmountDevice(image.ID, mountDir) + devices.RemoveDevice(image.ID) return err } - err = image.applyLayer(layerPath(root), mountDir) - if err != nil { - _ = devices.UnmountDevice(image.ID, mountDir) - _ = devices.RemoveDevice(image.ID) + if err = image.applyLayer(layerPath(root), mountDir); err != nil { + utils.Debugf("Error applying layer: %s", err) + devices.UnmountDevice(image.ID, mountDir) + devices.RemoveDevice(image.ID) return err } @@ -415,16 +415,15 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { _ = devices.RemoveDevice(image.ID) return err } - err = image.applyLayer(dockerinitLayer, mountDir) - if err != nil { - _ = devices.UnmountDevice(image.ID, mountDir) - _ = devices.RemoveDevice(image.ID) + + if err := image.applyLayer(dockerinitLayer, mountDir); err != nil { + devices.UnmountDevice(image.ID, mountDir) + devices.RemoveDevice(image.ID) return err } - err = devices.UnmountDevice(image.ID, mountDir) - if err != nil { - _ = devices.RemoveDevice(image.ID) + if err := devices.UnmountDevice(image.ID, mountDir); err != nil { + devices.RemoveDevice(image.ID) return err } @@ -455,8 +454,8 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { if err != nil { return err } - err = image.ensureImageDevice(devices) - if err != nil { + + if err := image.ensureImageDevice(devices); err != nil { return err } @@ -471,8 +470,7 @@ func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { } utils.Debugf("Mounting container %s at %s for container", id, root) - err = devices.MountDevice(id, root) - if err != nil { + if err := devices.MountDevice(id, root); err != nil { return err } diff --git a/server.go b/server.go index f3081db81a..9c5519cd50 100644 --- a/server.go +++ b/server.go @@ -1307,7 +1307,7 @@ func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors if err != nil { return nil, err } - srv := &Server{ + runtime.srv = &Server{ runtime: runtime, enableCors: enableCors, pullingPool: make(map[string]struct{}), @@ -1316,18 +1316,14 @@ func NewServer(flGraphPath string, deviceSet DeviceSet, autoRestart, enableCors listeners: make(map[string]chan utils.JSONMessage), reqFactory: nil, } - runtime.srv = srv - return srv, nil + return runtime.srv, nil } func (srv *Server) HTTPRequestFactory(metaHeaders map[string][]string) *utils.HTTPRequestFactory { if srv.reqFactory == nil { - ud := utils.NewHTTPUserAgentDecorator(srv.versionInfos()...) - md := &utils.HTTPMetaHeadersDecorator{ - Headers: metaHeaders, - } - factory := utils.NewHTTPRequestFactory(ud, md) - srv.reqFactory = factory + srv.reqFactory = utils.NewHTTPRequestFactory( + utils.NewHTTPUserAgentDecorator(srv.versionInfos()...), + &utils.HTTPMetaHeadersDecorator{Headers: metaHeaders}) } return srv.reqFactory } diff --git a/utils/utils.go b/utils/utils.go index 6daac53a03..138d5c9ca8 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -59,7 +59,15 @@ func Debugf(format string, a ...interface{}) { file = file[strings.LastIndex(file, "/")+1:] } - fmt.Fprintf(os.Stderr, fmt.Sprintf("[debug] %s:%d %s\n", file, line, format), a...) + _, file2, line2, ok := runtime.Caller(2) + if !ok { + file2 = "" + line2 = -1 + } else { + file2 = file2[strings.LastIndex(file2, "/")+1:] + } + + fmt.Fprintf(os.Stderr, fmt.Sprintf("[debug] %s:%d %s:%d %s\n", file, line, file2, line2, format), a...) } } From 7b58e15b08349dba35bf3813f77a0b900c2e4df5 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 3 Oct 2013 19:54:14 +0200 Subject: [PATCH 120/126] Be better at cleaning up leftover from earlier test runs When running the test inside a docker container we sometimes are left with leftover device nodes for device mapper devices that no longer exist. We were panic:ing in this case, but with this change we just remove such nodes. --- runtime_test.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/runtime_test.go b/runtime_test.go index 0a9b95a411..7c12832e30 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -10,6 +10,7 @@ import ( "log" "net" "os" + "path/filepath" "runtime" "strconv" "strings" @@ -85,6 +86,23 @@ func layerArchive(tarfile string) (io.Reader, error) { } // Remove any leftover device mapper devices from earlier runs of the unit tests +func removeDev(name string) { + path := filepath.Join("/dev/mapper", name) + fd, err := syscall.Open(path, syscall.O_RDONLY, 07777) + if err != nil { + if err == syscall.ENXIO { + // No device for this node, just remove it + os.Remove(path) + return + } + } else { + syscall.Close(fd) + } + if err := devmapper.RemoveDevice(name); err != nil { + panic(fmt.Errorf("Unable to remove existing device %s: %s", name, err)) + } +} + func cleanupDevMapper() { infos, _ := ioutil.ReadDir("/dev/mapper") if infos != nil { @@ -95,16 +113,12 @@ func cleanupDevMapper() { if name == "docker-unit-tests-devices-pool" { hasPool = true } else { - if err := devmapper.RemoveDevice(name); err != nil { - panic(fmt.Errorf("Unable to remove existing device %s: %s", name, err)) - } + removeDev(name) } } // We need to remove the pool last as the other devices block it if hasPool { - if err := devmapper.RemoveDevice("docker-unit-tests-devices-pool"); err != nil { - panic(fmt.Errorf("Unable to remove existing device docker-unit-tests-devices-pool: %s", name, err)) - } + removeDev("docker-unit-tests-devices-pool") } } } From 1a1be5a87c2169def7b0b0c29fc5cbac850a00a5 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 3 Oct 2013 21:00:16 +0200 Subject: [PATCH 121/126] Make sure we mark the libdevmapper /dev/mapper/control fd CLOEXEC We do a hack to mark it such, because otherwise lxc-start will not work. --- devmapper/deviceset_devmapper.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go index bad3b7a017..7e151a8a0d 100644 --- a/devmapper/deviceset_devmapper.go +++ b/devmapper/deviceset_devmapper.go @@ -10,6 +10,7 @@ import ( "os/exec" "path" "path/filepath" + "strconv" "strings" "syscall" ) @@ -564,6 +565,21 @@ func (devices *DeviceSetDM) setupBaseImage() error { return nil } +func setCloseOnExec(name string) { + fileInfos, _ := ioutil.ReadDir("/proc/self/fd") + if fileInfos != nil { + for _, i := range fileInfos { + link, _ := os.Readlink(filepath.Join("/proc/self/fd", i.Name())) + if link == name { + fd, err := strconv.Atoi(i.Name()) + if err == nil { + syscall.CloseOnExec(fd) + } + } + } + } +} + func (devices *DeviceSetDM) initDevmapper() error { info, err := devices.getInfo(devices.getPoolName()) if info == nil { @@ -572,6 +588,11 @@ func (devices *DeviceSetDM) initDevmapper() error { } utils.Debugf("initDevmapper(). Pool exists: %v", info.Exists) + // 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 info.Exists != 0 { /* Pool exists, assume everything is up */ if err := devices.loadMetaData(); err != nil { From b8439987184df45ee9571f9eb9d607518d96aeb8 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 3 Oct 2013 17:58:18 -0700 Subject: [PATCH 122/126] Small fixes --- api_test.go | 10 ++++++---- image.go | 15 +++++++-------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/api_test.go b/api_test.go index ae8602882f..7223411b2d 100644 --- a/api_test.go +++ b/api_test.go @@ -336,9 +336,11 @@ func TestGetContainersJSON(t *testing.T) { } r := httptest.NewRecorder() - if err := getContainersJSON(srv, APIVERSION, r, req, nil); err != nil { - t.Fatal(err) - } + setTimeout(t, "getContainerJSON timed out", 5*time.Second, func() { + if err := getContainersJSON(srv, APIVERSION, r, req, nil); err != nil { + t.Fatal(err) + } + }) containers := []APIContainers{} if err := json.Unmarshal(r.Body.Bytes(), &containers); err != nil { t.Fatal(err) @@ -374,7 +376,7 @@ func TestGetContainersExport(t *testing.T) { } r := httptest.NewRecorder() - if err = getContainersExport(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil { + if err := getContainersExport(srv, APIVERSION, r, nil, map[string]string{"name": container.ID}); err != nil { t.Fatal(err) } diff --git a/image.go b/image.go index 175bcfc6ae..a937c74086 100644 --- a/image.go +++ b/image.go @@ -310,7 +310,7 @@ func (image *Image) applyLayer(layer, target string) error { update := updateTimes[i] O_PATH := 010000000 // Not in syscall yet - var err error = nil + var err error if update.mode&syscall.S_IFLNK == syscall.S_IFLNK { // Update time on the symlink via O_PATH + futimes(), if supported by the kernel @@ -321,7 +321,7 @@ func (image *Image) applyLayer(layer, target string) error { return err } else { syscall.Futimes(fd, update.time) - _ = syscall.Close(fd) + syscall.Close(fd) } } else { err = syscall.Utimes(update.path, update.time) @@ -411,8 +411,8 @@ func (image *Image) ensureImageDevice(devices DeviceSet) error { // part of the container changes dockerinitLayer, err := image.getDockerInitLayer() if err != nil { - _ = devices.UnmountDevice(image.ID, mountDir) - _ = devices.RemoveDevice(image.ID) + devices.UnmountDevice(image.ID, mountDir) + devices.RemoveDevice(image.ID) return err } @@ -491,8 +491,7 @@ func (image *Image) Unmount(runtime *Runtime, root string, id string) error { return err } - err = devices.UnmountDevice(id, root) - if err != nil { + if err = devices.UnmountDevice(id, root); err != nil { return err } @@ -519,9 +518,9 @@ func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, er } changes, err := ChangesDirs(root, rw) - _ = devices.UnmountDevice(image.ID, rw) + devices.UnmountDevice(image.ID, rw) if !wasActivated { - _ = devices.DeactivateDevice(image.ID) + devices.DeactivateDevice(image.ID) } if err != nil { return nil, err From f29c500d8d480547874425ba98a9381fdc9df89e Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 3 Oct 2013 18:00:24 -0700 Subject: [PATCH 123/126] Small fixes --- deviceset.go | 59 ---------- devmapper/deviceset_devmapper.go | 1 + devmapper/devmapper.go | 182 +++++++++++++++++++------------ utils_test.go | 59 ++++++++++ 4 files changed, 172 insertions(+), 129 deletions(-) diff --git a/deviceset.go b/deviceset.go index 7aca816f43..b2a968738c 100644 --- a/deviceset.go +++ b/deviceset.go @@ -12,62 +12,3 @@ type DeviceSet interface { HasActivatedDevice(hash string) bool Shutdown() error } - -type DeviceSetWrapper struct { - wrapped DeviceSet - prefix string -} - -func (wrapper *DeviceSetWrapper) wrap(hash string) string { - if hash != "" { - hash = wrapper.prefix + "-" + hash - } - return hash -} - -func (wrapper *DeviceSetWrapper) AddDevice(hash, baseHash string) error { - return wrapper.wrapped.AddDevice(wrapper.wrap(hash), wrapper.wrap(baseHash)) -} - -func (wrapper *DeviceSetWrapper) SetInitialized(hash string) error { - return wrapper.wrapped.SetInitialized(wrapper.wrap(hash)) -} - -func (wrapper *DeviceSetWrapper) DeactivateDevice(hash string) error { - return wrapper.wrapped.DeactivateDevice(wrapper.wrap(hash)) -} - -func (wrapper *DeviceSetWrapper) Shutdown() error { - return nil -} - -func (wrapper *DeviceSetWrapper) RemoveDevice(hash string) error { - return wrapper.wrapped.RemoveDevice(wrapper.wrap(hash)) -} - -func (wrapper *DeviceSetWrapper) MountDevice(hash, path string) error { - return wrapper.wrapped.MountDevice(wrapper.wrap(hash), path) -} - -func (wrapper *DeviceSetWrapper) UnmountDevice(hash, path string) error { - return wrapper.wrapped.UnmountDevice(wrapper.wrap(hash), path) -} - -func (wrapper *DeviceSetWrapper) HasDevice(hash string) bool { - return wrapper.wrapped.HasDevice(wrapper.wrap(hash)) -} - -func (wrapper *DeviceSetWrapper) HasInitializedDevice(hash string) bool { - return wrapper.wrapped.HasInitializedDevice(wrapper.wrap(hash)) -} - -func (wrapper *DeviceSetWrapper) HasActivatedDevice(hash string) bool { - return wrapper.wrapped.HasActivatedDevice(wrapper.wrap(hash)) -} - -func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { - return &DeviceSetWrapper{ - wrapped: wrapped, - prefix: prefix, - } -} diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go index 7e151a8a0d..d9557659c7 100644 --- a/devmapper/deviceset_devmapper.go +++ b/devmapper/deviceset_devmapper.go @@ -442,6 +442,7 @@ func (devices *DeviceSetDM) registerDevice(id int, hash string, size uint64) (*D } func (devices *DeviceSetDM) activateDeviceIfNeeded(hash string) error { + utils.Debugf("activateDeviceIfNeeded()") info := devices.Devices[hash] if info == nil { return fmt.Errorf("Unknown device %s", hash) diff --git a/devmapper/devmapper.go b/devmapper/devmapper.go index 3799fd2df9..02922e836b 100644 --- a/devmapper/devmapper.go +++ b/devmapper/devmapper.go @@ -14,14 +14,13 @@ package devmapper #include #include -static char * -attach_loop_device(const char *filename, int *loop_fd_out) +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; + struct loop_info64 loopinfo = {0}; + struct stat st; + char buf[64]; + int i, loop_fd, fd, start_index; + char* loopname; *loop_fd_out = -1; @@ -37,6 +36,7 @@ attach_loop_device(const char *filename, int *loop_fd_out) fd = open(filename, O_RDWR); if (fd < 0) { + perror("open"); return NULL; } @@ -44,6 +44,7 @@ attach_loop_device(const char *filename, int *loop_fd_out) for (i = start_index ; loop_fd < 0 ; i++ ) { if (sprintf(buf, "/dev/loop%d", i) < 0) { close(fd); + perror("sprintf"); return NULL; } @@ -55,12 +56,14 @@ attach_loop_device(const char *filename, int *loop_fd_out) loop_fd = open(buf, O_RDWR); if (loop_fd < 0 && errno == ENOENT) { close(fd); + perror("open"); fprintf (stderr, "no available loopback device!"); return NULL; } else if (loop_fd < 0) continue; if (ioctl (loop_fd, LOOP_SET_FD, (void *)(size_t)fd) < 0) { + perror("ioctl"); close(loop_fd); loop_fd = -1; if (errno != EBUSY) { @@ -78,7 +81,10 @@ attach_loop_device(const char *filename, int *loop_fd_out) loopinfo.lo_flags = LO_FLAGS_AUTOCLEAR; if (ioctl(loop_fd, LOOP_SET_STATUS64, &loopinfo) < 0) { - ioctl(loop_fd, LOOP_CLR_FD, 0); + perror("ioctl1"); + if (ioctl(loop_fd, LOOP_CLR_FD, 0) < 0) { + perror("ioctl2"); + } close(loop_fd); fprintf (stderr, "cannot set up loopback device info"); return NULL; @@ -105,39 +111,19 @@ get_block_size(int fd) return (int64_t)size; } - */ import "C" import ( + "errors" "fmt" "github.com/dotcloud/docker/utils" "os" "runtime" + "syscall" "unsafe" ) -func SetDevDir(dir string) error { - c_dir := C.CString(dir) - defer C.free(unsafe.Pointer(c_dir)) - - if res := C.dm_set_dev_dir(c_dir); res != 1 { - utils.Debugf("Error dm_set_dev_dir") - return fmt.Errorf("dm_set_dev_dir failed") - } - return nil -} - -func GetLibraryVersion() (string, error) { - buffer := make([]byte, 128) - if res := C.dm_get_library_version((*C.char)(unsafe.Pointer(&buffer)), 128); res != 1 { - return "", fmt.Errorf("dm_get_library_version failed") - } - return string(buffer), nil -} - -type TaskType int - const ( DeviceCreate TaskType = iota DeviceReload @@ -160,22 +146,41 @@ const ( DeviceSetGeometry ) -type Task struct { - unmanaged *C.struct_dm_task -} +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") + ErrGetDriverVersion = errors.New("dm_task_get_driver_version failed") + 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") +) -type Info struct { - Exists int - Suspended int - LiveTable int - InactiveTable int - OpenCount int32 - EventNr uint32 - Major uint32 - Minor uint32 - ReadOnly int - TargetCount int32 -} +type ( + Task struct { + unmanaged *C.struct_dm_task + } + Info struct { + Exists int + Suspended int + LiveTable int + InactiveTable int + OpenCount int32 + EventNr uint32 + Major uint32 + Minor uint32 + ReadOnly int + TargetCount int32 + } + TaskType int +) func (t *Task) destroy() { if t != nil { @@ -196,37 +201,37 @@ func TaskCreate(tasktype TaskType) *Task { func (t *Task) Run() error { if res := C.dm_task_run(t.unmanaged); res != 1 { - return fmt.Errorf("dm_task_run failed") + return ErrTaskRun } return nil } func (t *Task) SetName(name string) error { c_name := C.CString(name) - defer C.free(unsafe.Pointer(c_name)) + defer free(c_name) if res := C.dm_task_set_name(t.unmanaged, c_name); res != 1 { if os.Getenv("DEBUG") != "" { C.perror(C.CString(fmt.Sprintf("[debug] Error dm_task_set_name(%s, %#v)", name, t.unmanaged))) } - return fmt.Errorf("dm_task_set_name failed") + return ErrTaskSetName } return nil } func (t *Task) SetMessage(message string) error { c_message := C.CString(message) - defer C.free(unsafe.Pointer(c_message)) + defer free(c_message) if res := C.dm_task_set_message(t.unmanaged, c_message); res != 1 { - return fmt.Errorf("dm_task_set_message failed") + return ErrTaskSetMessage } return nil } func (t *Task) SetSector(sector uint64) error { if res := C.dm_task_set_sector(t.unmanaged, C.uint64_t(sector)); res != 1 { - return fmt.Errorf("dm_task_set_add_node failed") + return ErrTaskSetAddNode } return nil } @@ -234,7 +239,7 @@ func (t *Task) SetSector(sector uint64) error { func (t *Task) SetCookie(cookie *uint32, flags uint16) error { c_cookie := C.uint32_t(*cookie) if res := C.dm_task_set_cookie(t.unmanaged, &c_cookie, C.uint16_t(flags)); res != 1 { - return fmt.Errorf("dm_task_set_add_node failed") + return ErrTaskSetAddNode } *cookie = uint32(c_cookie) return nil @@ -242,30 +247,30 @@ func (t *Task) SetCookie(cookie *uint32, flags uint16) error { func (t *Task) SetRo() error { if res := C.dm_task_set_ro(t.unmanaged); res != 1 { - return fmt.Errorf("dm_task_set_ro failed") + return ErrTaskSetRO } return nil } func (t *Task) AddTarget(start uint64, size uint64, ttype string, params string) error { c_ttype := C.CString(ttype) - defer C.free(unsafe.Pointer(c_ttype)) + defer free(c_ttype) c_params := C.CString(params) - defer C.free(unsafe.Pointer(c_params)) + defer free(c_params) if res := C.dm_task_add_target(t.unmanaged, C.uint64_t(start), C.uint64_t(size), c_ttype, c_params); res != 1 { - return fmt.Errorf("dm_task_add_target failed") + return ErrTaskAddTarget } return nil } func (t *Task) GetDriverVersion() (string, error) { - buffer := (*C.char)(C.malloc(128)) - defer C.free(unsafe.Pointer(buffer)) + buffer := C.CString(string(make([]byte, 128))) + defer free(buffer) if res := C.dm_task_get_driver_version(t.unmanaged, buffer, 128); res != 1 { - return "", fmt.Errorf("dm_task_get_driver_version") + return "", ErrGetDriverVersion } return C.GoString(buffer), nil } @@ -273,7 +278,7 @@ func (t *Task) GetDriverVersion() (string, error) { func (t *Task) GetInfo() (*Info, error) { c_info := C.struct_dm_info{} if res := C.dm_task_get_info(t.unmanaged, &c_info); res != 1 { - return nil, fmt.Errorf("dm_task_get_driver_version") + return nil, ErrGetDriverVersion } return &Info{ Exists: int(c_info.exists), @@ -301,29 +306,40 @@ func (t *Task) GetNextTarget(next uintptr) (uintptr, uint64, uint64, string, str func AttachLoopDevice(filename string) (*os.File, error) { c_filename := C.CString(filename) - defer C.free(unsafe.Pointer(c_filename)) + defer free(c_filename) var fd C.int res := C.attach_loop_device(c_filename, &fd) if res == nil { - return nil, fmt.Errorf("error loopback mounting") + return nil, ErrAttachLoopbackDevice } - defer C.free(unsafe.Pointer(res)) + defer free(res) + return os.NewFile(uintptr(fd), C.GoString(res)), nil } +func getBlockSize(fd uintptr) int { + var size uint64 + + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, C.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))); err != 0 { + utils.Debugf("Error ioctl: %s", err) + return -1 + } + return int(size) +} + func GetBlockDeviceSize(file *os.File) (uint64, error) { if size := C.get_block_size(C.int(file.Fd())); size == -1 { - return 0, fmt.Errorf("Can't get block size") + return 0, ErrGetBlockSize } else { return uint64(size), nil } } func UdevWait(cookie uint32) error { - res := C.dm_udev_wait(C.uint32_t(cookie)) - if res != 1 { - return fmt.Errorf("Failed to wait on udev cookie %d", cookie) + if res := C.dm_udev_wait(C.uint32_t(cookie)); res != 1 { + utils.Debugf("Failed to wait on udev cookie %d", cookie) + return ErrUdevWait } return nil } @@ -332,17 +348,43 @@ func LogInitVerbose(level int) { C.dm_log_init_verbose(C.int(level)) } +func SetDevDir(dir string) error { + c_dir := C.CString(dir) + defer free(c_dir) + + if res := C.dm_set_dev_dir(c_dir); res != 1 { + utils.Debugf("Error dm_set_dev_dir") + return ErrSetDevDir + } + return nil +} + +func GetLibraryVersion() (string, error) { + buffer := C.CString(string(make([]byte, 128))) + defer free(buffer) + + if res := C.dm_get_library_version(buffer, 128); res != 1 { + return "", ErrGetLibraryVersion + } + return C.GoString(buffer), nil +} + // Useful helper for cleanup func RemoveDevice(name string) error { task := TaskCreate(DeviceRemove) if task == nil { - return fmt.Errorf("Can't create task of type DeviceRemove") + return ErrCreateRemoveTask } if err := task.SetName(name); err != nil { - return fmt.Errorf("Can't set task name %s", name) + utils.Debugf("Can't set task name %s", name) + return err } if err := task.Run(); err != nil { - return fmt.Errorf("Error running removeDevice") + return ErrRunRemoveDevice } return nil } + +func free(p *C.char) { + C.free(unsafe.Pointer(p)) +} diff --git a/utils_test.go b/utils_test.go index f458b1da91..78968432e1 100644 --- a/utils_test.go +++ b/utils_test.go @@ -319,3 +319,62 @@ func TestParseLxcConfOpt(t *testing.T) { } } } + +type DeviceSetWrapper struct { + wrapped DeviceSet + prefix string +} + +func (wrapper *DeviceSetWrapper) wrap(hash string) string { + if hash != "" { + hash = wrapper.prefix + "-" + hash + } + return hash +} + +func (wrapper *DeviceSetWrapper) AddDevice(hash, baseHash string) error { + return wrapper.wrapped.AddDevice(wrapper.wrap(hash), wrapper.wrap(baseHash)) +} + +func (wrapper *DeviceSetWrapper) SetInitialized(hash string) error { + return wrapper.wrapped.SetInitialized(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) DeactivateDevice(hash string) error { + return wrapper.wrapped.DeactivateDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) Shutdown() error { + return nil +} + +func (wrapper *DeviceSetWrapper) RemoveDevice(hash string) error { + return wrapper.wrapped.RemoveDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) MountDevice(hash, path string) error { + return wrapper.wrapped.MountDevice(wrapper.wrap(hash), path) +} + +func (wrapper *DeviceSetWrapper) UnmountDevice(hash, path string) error { + return wrapper.wrapped.UnmountDevice(wrapper.wrap(hash), path) +} + +func (wrapper *DeviceSetWrapper) HasDevice(hash string) bool { + return wrapper.wrapped.HasDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) HasInitializedDevice(hash string) bool { + return wrapper.wrapped.HasInitializedDevice(wrapper.wrap(hash)) +} + +func (wrapper *DeviceSetWrapper) HasActivatedDevice(hash string) bool { + return wrapper.wrapped.HasActivatedDevice(wrapper.wrap(hash)) +} + +func NewDeviceSetWrapper(wrapped DeviceSet, prefix string) DeviceSet { + return &DeviceSetWrapper{ + wrapped: wrapped, + prefix: prefix, + } +} From f7e374fb3a51bd25e61ae6c4343838a0ca756fba Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 4 Oct 2013 15:36:30 +0200 Subject: [PATCH 124/126] Remove overly spewy Debugf --- devmapper/deviceset_devmapper.go | 1 - 1 file changed, 1 deletion(-) diff --git a/devmapper/deviceset_devmapper.go b/devmapper/deviceset_devmapper.go index 7e151a8a0d..110bfbf1a2 100644 --- a/devmapper/deviceset_devmapper.go +++ b/devmapper/deviceset_devmapper.go @@ -880,7 +880,6 @@ func (devices *DeviceSetDM) SetInitialized(hash string) error { } func (devices *DeviceSetDM) ensureInit() error { - utils.Debugf("ensureInit(). Initialized: %v", devices.initialized) if !devices.initialized { devices.initialized = true if err := devices.initDevmapper(); err != nil { From 9b65c7cf49feacad61cdcd11d2b002319a6b6bae Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 4 Oct 2013 15:38:47 +0200 Subject: [PATCH 125/126] hack: Don't just run the "TestRunHostname" test --- hack/make/test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/make/test b/hack/make/test index 1dadbdab8e..10264d9f7f 100644 --- a/hack/make/test +++ b/hack/make/test @@ -11,7 +11,7 @@ bundle_test() { for test_dir in $(find_test_dirs); do ( set -x cd $test_dir - DEBUG=1 go test -v -ldflags "$LDFLAGS" $BUILDFLAGS -run TestRunHostname + DEBUG=1 go test -v -ldflags "$LDFLAGS" $BUILDFLAGS ) done } 2>&1 | tee $DEST/test.log } From aaf1f73bcc2c594f07254a98ff77de3cc6351e92 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 4 Oct 2013 15:47:43 +0200 Subject: [PATCH 126/126] Tests: Initialize devicemapper early to avoid it happening in a test This can take a while and may cause some tests to timeout --- runtime_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/runtime_test.go b/runtime_test.go index 7c12832e30..07765e2d08 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -147,8 +147,13 @@ func init() { panic(err) } + deviceset := devmapper.NewDeviceSetDM(unitTestStoreDevicesBase) + // Create a device, which triggers the initiation of the base FS + // This avoids other tests doing this and timing out + deviceset.AddDevice("init","") + // Make it our Store root - if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, devmapper.NewDeviceSetDM(unitTestStoreDevicesBase), false); err != nil { + if runtime, err := NewRuntimeFromDirectory(unitTestStoreBase, deviceset, false); err != nil { panic(err) } else { globalRuntime = runtime