Skip to content

Commit f790629

Browse files
authored
Merge pull request #42 from wking/remove-before-unpacking
image/manifest: Recursively remove pre-existing entries when unpacking
2 parents ba6eae1 + bb40ed1 commit f790629

File tree

1 file changed

+103
-72
lines changed

1 file changed

+103
-72
lines changed

image/manifest.go

Lines changed: 103 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -218,100 +218,131 @@ loop:
218218
return errors.Wrapf(err, "error advancing tar stream")
219219
}
220220

221-
hdr.Name = filepath.Clean(hdr.Name)
222-
if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) {
223-
// Not the root directory, ensure that the parent directory exists
224-
parent := filepath.Dir(hdr.Name)
225-
parentPath := filepath.Join(dest, parent)
226-
if _, err2 := os.Lstat(parentPath); err2 != nil && os.IsNotExist(err2) {
227-
if err3 := os.MkdirAll(parentPath, 0755); err3 != nil {
228-
return err3
229-
}
230-
}
231-
}
232-
path := filepath.Join(dest, hdr.Name)
233-
if entries[path] {
234-
return fmt.Errorf("duplicate entry for %s", path)
235-
}
236-
entries[path] = true
237-
rel, err := filepath.Rel(dest, path)
221+
var whiteout bool
222+
whiteout, err = unpackLayerEntry(dest, hdr, tr, &entries)
238223
if err != nil {
239224
return err
240225
}
241-
info := hdr.FileInfo()
242-
if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
243-
return fmt.Errorf("%q is outside of %q", hdr.Name, dest)
226+
if whiteout {
227+
continue loop
244228
}
245229

246-
if strings.HasPrefix(info.Name(), ".wh.") {
247-
path = strings.Replace(path, ".wh.", "", 1)
230+
// Directory mtimes must be handled at the end to avoid further
231+
// file creation in them to modify the directory mtime
232+
if hdr.Typeflag == tar.TypeDir {
233+
dirs = append(dirs, hdr)
234+
}
235+
}
236+
for _, hdr := range dirs {
237+
path := filepath.Join(dest, hdr.Name)
238+
239+
finfo := hdr.FileInfo()
240+
// I believe the old version was using time.Now().UTC() to overcome an
241+
// invalid error from chtimes.....but here we lose hdr.AccessTime like this...
242+
if err := os.Chtimes(path, time.Now().UTC(), finfo.ModTime()); err != nil {
243+
return errors.Wrap(err, "error changing time")
244+
}
245+
}
246+
return nil
247+
}
248248

249-
if err := os.RemoveAll(path); err != nil {
250-
return errors.Wrap(err, "unable to delete whiteout path")
249+
// unpackLayerEntry unpacks a single entry from a layer.
250+
func unpackLayerEntry(dest string, header *tar.Header, reader io.Reader, entries *map[string]bool) (whiteout bool, err error) {
251+
header.Name = filepath.Clean(header.Name)
252+
if !strings.HasSuffix(header.Name, string(os.PathSeparator)) {
253+
// Not the root directory, ensure that the parent directory exists
254+
parent := filepath.Dir(header.Name)
255+
parentPath := filepath.Join(dest, parent)
256+
if _, err2 := os.Lstat(parentPath); err2 != nil && os.IsNotExist(err2) {
257+
if err3 := os.MkdirAll(parentPath, 0755); err3 != nil {
258+
return false, err3
251259
}
260+
}
261+
}
262+
path := filepath.Join(dest, header.Name)
263+
if (*entries)[path] {
264+
return false, fmt.Errorf("duplicate entry for %s", path)
265+
}
266+
(*entries)[path] = true
267+
rel, err := filepath.Rel(dest, path)
268+
if err != nil {
269+
return false, err
270+
}
271+
info := header.FileInfo()
272+
if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
273+
return false, fmt.Errorf("%q is outside of %q", header.Name, dest)
274+
}
252275

253-
continue loop
276+
if strings.HasPrefix(info.Name(), ".wh.") {
277+
path = strings.Replace(path, ".wh.", "", 1)
278+
279+
if err = os.RemoveAll(path); err != nil {
280+
return true, errors.Wrap(err, "unable to delete whiteout path")
254281
}
255282

256-
switch hdr.Typeflag {
257-
case tar.TypeDir:
258-
if fi, err := os.Lstat(path); !(err == nil && fi.IsDir()) {
259-
if err2 := os.MkdirAll(path, info.Mode()); err2 != nil {
260-
return errors.Wrap(err2, "error creating directory")
261-
}
262-
}
283+
return true, nil
284+
}
263285

264-
case tar.TypeReg, tar.TypeRegA:
265-
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, info.Mode())
286+
if header.Typeflag != tar.TypeDir {
287+
err = os.RemoveAll(path)
288+
if err != nil && !os.IsNotExist(err) {
289+
return false, err
290+
}
291+
}
292+
293+
switch header.Typeflag {
294+
case tar.TypeDir:
295+
fi, err := os.Lstat(path)
296+
if err != nil && !os.IsNotExist(err) {
297+
return false, err
298+
}
299+
if os.IsNotExist(err) || !fi.IsDir() {
300+
err = os.RemoveAll(path)
301+
if err != nil && !os.IsNotExist(err) {
302+
return false, err
303+
}
304+
err = os.MkdirAll(path, info.Mode())
266305
if err != nil {
267-
return errors.Wrap(err, "unable to open file")
306+
return false, err
268307
}
308+
}
269309

270-
if _, err := io.Copy(f, tr); err != nil {
271-
f.Close()
272-
return errors.Wrap(err, "unable to copy")
273-
}
274-
f.Close()
310+
case tar.TypeReg, tar.TypeRegA:
311+
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, info.Mode())
312+
if err != nil {
313+
return false, errors.Wrap(err, "unable to open file")
314+
}
275315

276-
case tar.TypeLink:
277-
target := filepath.Join(dest, hdr.Linkname)
316+
if _, err := io.Copy(f, reader); err != nil {
317+
f.Close()
318+
return false, errors.Wrap(err, "unable to copy")
319+
}
320+
f.Close()
278321

279-
if !strings.HasPrefix(target, dest) {
280-
return fmt.Errorf("invalid hardlink %q -> %q", target, hdr.Linkname)
281-
}
322+
case tar.TypeLink:
323+
target := filepath.Join(dest, header.Linkname)
282324

283-
if err := os.Link(target, path); err != nil {
284-
return err
285-
}
325+
if !strings.HasPrefix(target, dest) {
326+
return false, fmt.Errorf("invalid hardlink %q -> %q", target, header.Linkname)
327+
}
286328

287-
case tar.TypeSymlink:
288-
target := filepath.Join(filepath.Dir(path), hdr.Linkname)
329+
if err := os.Link(target, path); err != nil {
330+
return false, err
331+
}
289332

290-
if !strings.HasPrefix(target, dest) {
291-
return fmt.Errorf("invalid symlink %q -> %q", path, hdr.Linkname)
292-
}
333+
case tar.TypeSymlink:
334+
target := filepath.Join(filepath.Dir(path), header.Linkname)
293335

294-
if err := os.Symlink(hdr.Linkname, path); err != nil {
295-
return err
296-
}
297-
case tar.TypeXGlobalHeader:
298-
return nil
299-
}
300-
// Directory mtimes must be handled at the end to avoid further
301-
// file creation in them to modify the directory mtime
302-
if hdr.Typeflag == tar.TypeDir {
303-
dirs = append(dirs, hdr)
336+
if !strings.HasPrefix(target, dest) {
337+
return false, fmt.Errorf("invalid symlink %q -> %q", path, header.Linkname)
304338
}
305-
}
306-
for _, hdr := range dirs {
307-
path := filepath.Join(dest, hdr.Name)
308339

309-
finfo := hdr.FileInfo()
310-
// I believe the old version was using time.Now().UTC() to overcome an
311-
// invalid error from chtimes.....but here we lose hdr.AccessTime like this...
312-
if err := os.Chtimes(path, time.Now().UTC(), finfo.ModTime()); err != nil {
313-
return errors.Wrap(err, "error changing time")
340+
if err := os.Symlink(header.Linkname, path); err != nil {
341+
return false, err
314342
}
343+
case tar.TypeXGlobalHeader:
344+
return false, nil
315345
}
316-
return nil
346+
347+
return false, nil
317348
}

0 commit comments

Comments
 (0)