123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672 |
- package docker
- import (
- "crypto/rand"
- "encoding/hex"
- "encoding/json"
- "fmt"
- "github.com/dotcloud/docker/utils"
- "github.com/dotcloud/docker/devmapper"
- "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
- // FIXME: once an image is added into device mapper, the layer is no longer needed
- 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 *devmapper.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, false); err != nil {
- utils.Debugf("Error mounting device: %s", err)
- 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
- }
- 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)
- if err := devices.MountDevice(id, root, false); err != nil {
- 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, true)
- 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
- }
|