mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
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
This commit is contained in:
parent
53851474c0
commit
fcd41fe51a
1 changed files with 260 additions and 9 deletions
259
image.go
259
image.go
|
@ -15,6 +15,7 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -136,6 +137,10 @@ func jsonPath(root string) string {
|
||||||
return path.Join(root, "json")
|
return path.Join(root, "json")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mountPath(root string) string {
|
||||||
|
return path.Join(root, "mount")
|
||||||
|
}
|
||||||
|
|
||||||
func MountAUFS(ro []string, rw string, target string) error {
|
func MountAUFS(ro []string, rw string, target string) error {
|
||||||
// FIXME: Now mount the layers
|
// FIXME: Now mount the layers
|
||||||
rwBranch := fmt.Sprintf("%v=rw", rw)
|
rwBranch := fmt.Sprintf("%v=rw", rw)
|
||||||
|
@ -170,26 +175,272 @@ func (image *Image) TarLayer(compression Compression) (Archive, error) {
|
||||||
return Tar(layerPath, compression)
|
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 {
|
func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error {
|
||||||
if mounted, err := Mounted(root); err != nil {
|
if mounted, err := Mounted(root); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if mounted {
|
} else if mounted {
|
||||||
return fmt.Errorf("%s is already mounted", root)
|
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
|
// Create the target directories if they don't exist
|
||||||
if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) {
|
if err := os.Mkdir(root, 0755); err != nil && !os.IsExist(err) {
|
||||||
return 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) {
|
if err := os.Mkdir(rw, 0755); err != nil && !os.IsExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
layers, err := image.layers()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := MountAUFS(layers, rw, root); err != nil {
|
if err := MountAUFS(layers, rw, root); err != nil {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue