Tizen_4.0 base
[platform/upstream/docker-engine.git] / pkg / archive / diff.go
1 package archive
2
3 import (
4         "archive/tar"
5         "fmt"
6         "io"
7         "io/ioutil"
8         "os"
9         "path/filepath"
10         "runtime"
11         "strings"
12
13         "github.com/Sirupsen/logrus"
14         "github.com/docker/docker/pkg/idtools"
15         "github.com/docker/docker/pkg/pools"
16         "github.com/docker/docker/pkg/system"
17 )
18
19 // UnpackLayer unpack `layer` to a `dest`. The stream `layer` can be
20 // compressed or uncompressed.
21 // Returns the size in bytes of the contents of the layer.
22 func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, err error) {
23         tr := tar.NewReader(layer)
24         trBuf := pools.BufioReader32KPool.Get(tr)
25         defer pools.BufioReader32KPool.Put(trBuf)
26
27         var dirs []*tar.Header
28         unpackedPaths := make(map[string]struct{})
29
30         if options == nil {
31                 options = &TarOptions{}
32         }
33         if options.ExcludePatterns == nil {
34                 options.ExcludePatterns = []string{}
35         }
36         idMappings := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps)
37
38         aufsTempdir := ""
39         aufsHardlinks := make(map[string]*tar.Header)
40
41         // Iterate through the files in the archive.
42         for {
43                 hdr, err := tr.Next()
44                 if err == io.EOF {
45                         // end of tar archive
46                         break
47                 }
48                 if err != nil {
49                         return 0, err
50                 }
51
52                 size += hdr.Size
53
54                 // Normalize name, for safety and for a simple is-root check
55                 hdr.Name = filepath.Clean(hdr.Name)
56
57                 // Windows does not support filenames with colons in them. Ignore
58                 // these files. This is not a problem though (although it might
59                 // appear that it is). Let's suppose a client is running docker pull.
60                 // The daemon it points to is Windows. Would it make sense for the
61                 // client to be doing a docker pull Ubuntu for example (which has files
62                 // with colons in the name under /usr/share/man/man3)? No, absolutely
63                 // not as it would really only make sense that they were pulling a
64                 // Windows image. However, for development, it is necessary to be able
65                 // to pull Linux images which are in the repository.
66                 //
67                 // TODO Windows. Once the registry is aware of what images are Windows-
68                 // specific or Linux-specific, this warning should be changed to an error
69                 // to cater for the situation where someone does manage to upload a Linux
70                 // image but have it tagged as Windows inadvertently.
71                 if runtime.GOOS == "windows" {
72                         if strings.Contains(hdr.Name, ":") {
73                                 logrus.Warnf("Windows: Ignoring %s (is this a Linux image?)", hdr.Name)
74                                 continue
75                         }
76                 }
77
78                 // Note as these operations are platform specific, so must the slash be.
79                 if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) {
80                         // Not the root directory, ensure that the parent directory exists.
81                         // This happened in some tests where an image had a tarfile without any
82                         // parent directories.
83                         parent := filepath.Dir(hdr.Name)
84                         parentPath := filepath.Join(dest, parent)
85
86                         if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
87                                 err = system.MkdirAll(parentPath, 0600, "")
88                                 if err != nil {
89                                         return 0, err
90                                 }
91                         }
92                 }
93
94                 // Skip AUFS metadata dirs
95                 if strings.HasPrefix(hdr.Name, WhiteoutMetaPrefix) {
96                         // Regular files inside /.wh..wh.plnk can be used as hardlink targets
97                         // We don't want this directory, but we need the files in them so that
98                         // such hardlinks can be resolved.
99                         if strings.HasPrefix(hdr.Name, WhiteoutLinkDir) && hdr.Typeflag == tar.TypeReg {
100                                 basename := filepath.Base(hdr.Name)
101                                 aufsHardlinks[basename] = hdr
102                                 if aufsTempdir == "" {
103                                         if aufsTempdir, err = ioutil.TempDir("", "dockerplnk"); err != nil {
104                                                 return 0, err
105                                         }
106                                         defer os.RemoveAll(aufsTempdir)
107                                 }
108                                 if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, true, nil, options.InUserNS); err != nil {
109                                         return 0, err
110                                 }
111                         }
112
113                         if hdr.Name != WhiteoutOpaqueDir {
114                                 continue
115                         }
116                 }
117                 path := filepath.Join(dest, hdr.Name)
118                 rel, err := filepath.Rel(dest, path)
119                 if err != nil {
120                         return 0, err
121                 }
122
123                 // Note as these operations are platform specific, so must the slash be.
124                 if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
125                         return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
126                 }
127                 base := filepath.Base(path)
128
129                 if strings.HasPrefix(base, WhiteoutPrefix) {
130                         dir := filepath.Dir(path)
131                         if base == WhiteoutOpaqueDir {
132                                 _, err := os.Lstat(dir)
133                                 if err != nil {
134                                         return 0, err
135                                 }
136                                 err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
137                                         if err != nil {
138                                                 if os.IsNotExist(err) {
139                                                         err = nil // parent was deleted
140                                                 }
141                                                 return err
142                                         }
143                                         if path == dir {
144                                                 return nil
145                                         }
146                                         if _, exists := unpackedPaths[path]; !exists {
147                                                 err := os.RemoveAll(path)
148                                                 return err
149                                         }
150                                         return nil
151                                 })
152                                 if err != nil {
153                                         return 0, err
154                                 }
155                         } else {
156                                 originalBase := base[len(WhiteoutPrefix):]
157                                 originalPath := filepath.Join(dir, originalBase)
158                                 if err := os.RemoveAll(originalPath); err != nil {
159                                         return 0, err
160                                 }
161                         }
162                 } else {
163                         // If path exits we almost always just want to remove and replace it.
164                         // The only exception is when it is a directory *and* the file from
165                         // the layer is also a directory. Then we want to merge them (i.e.
166                         // just apply the metadata from the layer).
167                         if fi, err := os.Lstat(path); err == nil {
168                                 if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
169                                         if err := os.RemoveAll(path); err != nil {
170                                                 return 0, err
171                                         }
172                                 }
173                         }
174
175                         trBuf.Reset(tr)
176                         srcData := io.Reader(trBuf)
177                         srcHdr := hdr
178
179                         // Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so
180                         // we manually retarget these into the temporary files we extracted them into
181                         if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), WhiteoutLinkDir) {
182                                 linkBasename := filepath.Base(hdr.Linkname)
183                                 srcHdr = aufsHardlinks[linkBasename]
184                                 if srcHdr == nil {
185                                         return 0, fmt.Errorf("Invalid aufs hardlink")
186                                 }
187                                 tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename))
188                                 if err != nil {
189                                         return 0, err
190                                 }
191                                 defer tmpFile.Close()
192                                 srcData = tmpFile
193                         }
194
195                         if err := remapIDs(idMappings, srcHdr); err != nil {
196                                 return 0, err
197                         }
198
199                         if err := createTarFile(path, dest, srcHdr, srcData, true, nil, options.InUserNS); err != nil {
200                                 return 0, err
201                         }
202
203                         // Directory mtimes must be handled at the end to avoid further
204                         // file creation in them to modify the directory mtime
205                         if hdr.Typeflag == tar.TypeDir {
206                                 dirs = append(dirs, hdr)
207                         }
208                         unpackedPaths[path] = struct{}{}
209                 }
210         }
211
212         for _, hdr := range dirs {
213                 path := filepath.Join(dest, hdr.Name)
214                 if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
215                         return 0, err
216                 }
217         }
218
219         return size, nil
220 }
221
222 // ApplyLayer parses a diff in the standard layer format from `layer`,
223 // and applies it to the directory `dest`. The stream `layer` can be
224 // compressed or uncompressed.
225 // Returns the size in bytes of the contents of the layer.
226 func ApplyLayer(dest string, layer io.Reader) (int64, error) {
227         return applyLayerHandler(dest, layer, &TarOptions{}, true)
228 }
229
230 // ApplyUncompressedLayer parses a diff in the standard layer format from
231 // `layer`, and applies it to the directory `dest`. The stream `layer`
232 // can only be uncompressed.
233 // Returns the size in bytes of the contents of the layer.
234 func ApplyUncompressedLayer(dest string, layer io.Reader, options *TarOptions) (int64, error) {
235         return applyLayerHandler(dest, layer, options, false)
236 }
237
238 // do the bulk load of ApplyLayer, but allow for not calling DecompressStream
239 func applyLayerHandler(dest string, layer io.Reader, options *TarOptions, decompress bool) (int64, error) {
240         dest = filepath.Clean(dest)
241
242         // We need to be able to set any perms
243         oldmask, err := system.Umask(0)
244         if err != nil {
245                 return 0, err
246         }
247         defer system.Umask(oldmask) // ignore err, ErrNotSupportedPlatform
248
249         if decompress {
250                 layer, err = DecompressStream(layer)
251                 if err != nil {
252                         return 0, err
253                 }
254         }
255         return UnpackLayer(dest, layer, options)
256 }