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