Explorar o código

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
Alexander Larsson %!s(int64=12) %!d(string=hai) anos
pai
achega
d2ba3e2005
Modificáronse 1 ficheiros con 260 adicións e 9 borrados
  1. 260 9
      image.go

+ 260 - 9
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,26 +175,272 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) {
 	return Tar(layerPath, compression)
 }
 
-func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error {
-	if mounted, err := Mounted(root); err != nil {
+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
-	} else if mounted {
-		return fmt.Errorf("%s is already mounted", root)
 	}
-	layers, err := image.layers()
+
+	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
 	}
-	// Create the target directories if they don't exist
-	if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(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
 	}
-	if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(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)
 	}
-	if err := MountAUFS(layers, rw, root); err != nil {
+	// 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")
+
+	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
 }