| package docker |
| |
| import ( |
| "crypto/rand" |
| "encoding/hex" |
| "encoding/json" |
| "fmt" |
| "github.com/dotcloud/docker/utils" |
| "io" |
| "io/ioutil" |
| "os" |
| "path" |
| "path/filepath" |
| "strconv" |
| "strings" |
| "syscall" |
| "time" |
| ) |
| |
| type Image struct { |
| ID string `json:"id"` |
| Parent string `json:"parent,omitempty"` |
| Comment string `json:"comment,omitempty"` |
| Created time.Time `json:"created"` |
| Container string `json:"container,omitempty"` |
| ContainerConfig Config `json:"container_config,omitempty"` |
| DockerVersion string `json:"docker_version,omitempty"` |
| Author string `json:"author,omitempty"` |
| Config *Config `json:"config,omitempty"` |
| Architecture string `json:"architecture,omitempty"` |
| graph *Graph |
| Size int64 |
| } |
| |
| func LoadImage(root string) (*Image, error) { |
| // Load the json data |
| jsonData, err := ioutil.ReadFile(jsonPath(root)) |
| if err != nil { |
| return nil, err |
| } |
| img := &Image{} |
| |
| if err := json.Unmarshal(jsonData, img); err != nil { |
| return nil, err |
| } |
| if err := ValidateID(img.ID); err != nil { |
| return nil, err |
| } |
| |
| if buf, err := ioutil.ReadFile(path.Join(root, "layersize")); err != nil { |
| if !os.IsNotExist(err) { |
| return nil, err |
| } |
| } else { |
| if size, err := strconv.Atoi(string(buf)); err != nil { |
| return nil, err |
| } else { |
| img.Size = int64(size) |
| } |
| } |
| |
| // Check that the filesystem layer exists |
| if stat, err := os.Stat(layerPath(root)); err != nil { |
| if os.IsNotExist(err) { |
| return nil, fmt.Errorf("Couldn't load image %s: no filesystem layer", img.ID) |
| } |
| return nil, err |
| } else if !stat.IsDir() { |
| return nil, fmt.Errorf("Couldn't load image %s: %s is not a directory", img.ID, layerPath(root)) |
| } |
| return img, nil |
| } |
| |
| func StoreImage(img *Image, jsonData []byte, layerData Archive, root string) error { |
| // Check that root doesn't already exist |
| if _, err := os.Stat(root); err == nil { |
| return fmt.Errorf("Image %s already exists", img.ID) |
| } else if !os.IsNotExist(err) { |
| return err |
| } |
| // Store the layer |
| layer := layerPath(root) |
| if err := os.MkdirAll(layer, 0755); err != nil { |
| return err |
| } |
| |
| // If layerData is not nil, unpack it into the new layer |
| if layerData != nil { |
| start := time.Now() |
| utils.Debugf("Start untar layer") |
| if err := Untar(layerData, layer); err != nil { |
| return err |
| } |
| utils.Debugf("Untar time: %vs\n", time.Now().Sub(start).Seconds()) |
| } |
| |
| // If raw json is provided, then use it |
| if jsonData != nil { |
| return ioutil.WriteFile(jsonPath(root), jsonData, 0600) |
| } else { // Otherwise, unmarshal the image |
| jsonData, err := json.Marshal(img) |
| if err != nil { |
| return err |
| } |
| if err := ioutil.WriteFile(jsonPath(root), jsonData, 0600); err != nil { |
| return err |
| } |
| } |
| |
| return StoreSize(img, root) |
| } |
| |
| func StoreSize(img *Image, root string) error { |
| layer := layerPath(root) |
| |
| var totalSize int64 = 0 |
| filepath.Walk(layer, func(path string, fileInfo os.FileInfo, err error) error { |
| totalSize += fileInfo.Size() |
| return nil |
| }) |
| img.Size = totalSize |
| |
| if err := ioutil.WriteFile(path.Join(root, "layersize"), []byte(strconv.Itoa(int(totalSize))), 0600); err != nil { |
| return nil |
| } |
| |
| return nil |
| } |
| |
| func layerPath(root string) string { |
| return path.Join(root, "layer") |
| } |
| |
| func jsonPath(root string) string { |
| return path.Join(root, "json") |
| } |
| |
| func mountPath(root string) string { |
| return path.Join(root, "mount") |
| } |
| |
| // TarLayer returns a tar archive of the image's filesystem layer. |
| func (image *Image) TarLayer(compression Compression) (Archive, error) { |
| layerPath, err := image.layer() |
| if err != nil { |
| return nil, err |
| } |
| return Tar(layerPath, compression) |
| } |
| |
| type TimeUpdate struct { |
| path string |
| time []syscall.Timeval |
| mode uint32 |
| } |
| |
| 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 { |
| 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&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) |
| if err != nil { |
| _ = dstFile.Close() |
| return err |
| } |
| err = CopyFile(dstFile, srcFile) |
| _ = dstFile.Close() |
| _ = srcFile.Close() |
| if err != nil { |
| return err |
| } |
| } else { |
| 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.Chmod(targetPath, srcStat.Mode&07777) |
| if err != nil { |
| return err |
| } |
| } |
| |
| ts := []syscall.Timeval{ |
| syscall.NsecToTimeval(srcStat.Atim.Nano()), |
| syscall.NsecToTimeval(srcStat.Mtim.Nano()), |
| } |
| |
| u := TimeUpdate{ |
| path: targetPath, |
| time: ts, |
| mode: srcStat.Mode, |
| } |
| |
| // Delay time updates until all other changes done, or it is |
| // overwritten for directories (by child changes) |
| updateTimes = append(updateTimes, u) |
| } |
| return nil |
| }) |
| 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 |
| 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 |
| |
| 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 |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| 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 { |
| utils.Debugf("Image %s is unexpectedly mounted, unmounting...", image.ID) |
| err = syscall.Unmount(mountDir, 0) |
| if err != nil { |
| return err |
| } |
| } |
| |
| if devices.HasDevice(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 |
| } |
| } |
| |
| utils.Debugf("Creating device-mapper device for image id %s", image.ID) |
| if err := devices.AddDevice(image.ID, image.Parent); err != nil { |
| utils.Debugf("Error add device: %s", err) |
| return err |
| } |
| |
| if err := devices.MountDevice(image.ID, mountDir); err != nil { |
| utils.Debugf("Error mounting device: %s", err) |
| devices.RemoveDevice(image.ID) |
| return err |
| } |
| |
| 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, true) |
| devices.RemoveDevice(image.ID) |
| return err |
| } |
| |
| if err = image.applyLayer(layerPath(root), mountDir); err != nil { |
| utils.Debugf("Error applying layer: %s", err) |
| devices.UnmountDevice(image.ID, mountDir, true) |
| devices.RemoveDevice(image.ID) |
| 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.UnmountDevice(image.ID, mountDir, true) |
| devices.RemoveDevice(image.ID) |
| return err |
| } |
| |
| if err := image.applyLayer(dockerinitLayer, mountDir); err != nil { |
| devices.UnmountDevice(image.ID, mountDir, true) |
| devices.RemoveDevice(image.ID) |
| return err |
| } |
| |
| if err := devices.UnmountDevice(image.ID, mountDir, true); err != nil { |
| devices.RemoveDevice(image.ID) |
| return err |
| } |
| |
| devices.SetInitialized(image.ID) |
| |
| return nil |
| } |
| |
| func (image *Image) Mounted(runtime *Runtime, root, rw string) (bool, error) { |
| return Mounted(root) |
| } |
| |
| func (image *Image) Mount(runtime *Runtime, root, rw string, id string) error { |
| 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 |
| } |
| |
| devices, err := runtime.GetDeviceSet() |
| if err != nil { |
| return err |
| } |
| |
| if err := image.ensureImageDevice(devices); 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) |
| if err := devices.MountDevice(id, root); 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 |
| } |
| |
| func (image *Image) Unmount(runtime *Runtime, root string, id string) error { |
| // Try to deactivate the device as generally there is no use for it anymore |
| devices, err := runtime.GetDeviceSet() |
| if err != nil { |
| return err |
| } |
| |
| if err = devices.UnmountDevice(id, root, true); err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| func (image *Image) Changes(runtime *Runtime, root, rw, id string) ([]Change, error) { |
| 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, !wasActivated) |
| if err != nil { |
| return nil, err |
| } |
| return changes, nil |
| } |
| |
| func (image *Image) ExportChanges(runtime *Runtime, root, rw, id string) (Archive, error) { |
| 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) |
| } |
| |
| func (image *Image) ShortID() string { |
| return utils.TruncateID(image.ID) |
| } |
| |
| func ValidateID(id string) error { |
| if id == "" { |
| return fmt.Errorf("Image id can't be empty") |
| } |
| if strings.Contains(id, ":") { |
| return fmt.Errorf("Invalid character in image id: ':'") |
| } |
| return nil |
| } |
| |
| func GenerateID() string { |
| id := make([]byte, 32) |
| _, err := io.ReadFull(rand.Reader, id) |
| if err != nil { |
| panic(err) // This shouldn't happen |
| } |
| return hex.EncodeToString(id) |
| } |
| |
| // Image includes convenience proxy functions to its graph |
| // These functions will return an error if the image is not registered |
| // (ie. if image.graph == nil) |
| func (img *Image) History() ([]*Image, error) { |
| var parents []*Image |
| if err := img.WalkHistory( |
| func(img *Image) error { |
| parents = append(parents, img) |
| return nil |
| }, |
| ); err != nil { |
| return nil, err |
| } |
| return parents, nil |
| } |
| |
| // layers returns all the filesystem layers needed to mount an image |
| // FIXME: @shykes refactor this function with the new error handling |
| // (I'll do it if I have time tonight, I focus on the rest) |
| func (img *Image) layers() ([]string, error) { |
| var list []string |
| var e error |
| if err := img.WalkHistory( |
| func(img *Image) (err error) { |
| if layer, err := img.layer(); err != nil { |
| e = err |
| } else if layer != "" { |
| list = append(list, layer) |
| } |
| return err |
| }, |
| ); err != nil { |
| return nil, err |
| } else if e != nil { // Did an error occur inside the handler? |
| return nil, e |
| } |
| if len(list) == 0 { |
| return nil, fmt.Errorf("No layer found for image %s\n", img.ID) |
| } |
| |
| // Inject the dockerinit layer (empty place-holder for mount-binding dockerinit) |
| if dockerinitLayer, err := img.getDockerInitLayer(); err != nil { |
| return nil, err |
| } else { |
| list = append([]string{dockerinitLayer}, list...) |
| } |
| return list, nil |
| } |
| |
| func (img *Image) WalkHistory(handler func(*Image) error) (err error) { |
| currentImg := img |
| for currentImg != nil { |
| if handler != nil { |
| if err := handler(currentImg); err != nil { |
| return err |
| } |
| } |
| currentImg, err = currentImg.GetParent() |
| if err != nil { |
| return fmt.Errorf("Error while getting parent image: %v", err) |
| } |
| } |
| return nil |
| } |
| |
| func (img *Image) GetParent() (*Image, error) { |
| if img.Parent == "" { |
| return nil, nil |
| } |
| if img.graph == nil { |
| return nil, fmt.Errorf("Can't lookup parent of unregistered image") |
| } |
| return img.graph.Get(img.Parent) |
| } |
| |
| func (img *Image) getDockerInitLayer() (string, error) { |
| if img.graph == nil { |
| return "", fmt.Errorf("Can't lookup dockerinit layer of unregistered image") |
| } |
| return img.graph.getDockerInitLayer() |
| } |
| |
| func (img *Image) root() (string, error) { |
| if img.graph == nil { |
| return "", fmt.Errorf("Can't lookup root of unregistered image") |
| } |
| return img.graph.imageRoot(img.ID), nil |
| } |
| |
| // Return the path of an image's layer |
| func (img *Image) layer() (string, error) { |
| root, err := img.root() |
| if err != nil { |
| return "", err |
| } |
| return layerPath(root), nil |
| } |
| |
| func (img *Image) getParentsSize(size int64) int64 { |
| parentImage, err := img.GetParent() |
| if err != nil || parentImage == nil { |
| return size |
| } |
| size += parentImage.Size |
| return parentImage.getParentsSize(size) |
| } |
| |
| // Build an Image object from raw json data |
| func NewImgJSON(src []byte) (*Image, error) { |
| ret := &Image{} |
| |
| utils.Debugf("Json string: {%s}\n", src) |
| // FIXME: Is there a cleaner way to "purify" the input json? |
| if err := json.Unmarshal(src, ret); err != nil { |
| return nil, err |
| } |
| return ret, nil |
| } |