undocker/rootfs/rootfs.go

163 lines
3.4 KiB
Go
Raw Normal View History

2021-05-24 00:11:57 +03:00
package rootfs
2021-05-24 00:11:57 +03:00
import (
2021-05-24 00:11:57 +03:00
"archive/tar"
"encoding/json"
2021-05-24 00:11:57 +03:00
"errors"
2021-05-24 00:11:57 +03:00
"io"
2021-05-24 00:11:57 +03:00
"path/filepath"
2021-05-24 00:11:57 +03:00
"strings"
2021-05-24 00:11:57 +03:00
"go.uber.org/multierr"
2021-05-24 00:11:57 +03:00
)
2021-05-24 00:11:57 +03:00
const (
_manifestJSON = "manifest.json"
)
2021-05-24 00:11:57 +03:00
var (
ErrBadManifest = errors.New("bad or missing manifest.json")
)
2021-05-24 00:11:57 +03:00
type dockerManifestJSON []struct {
2021-05-24 00:11:57 +03:00
Config string `json:"Config,omitempty"`
2021-05-24 00:11:57 +03:00
Layers []string `json:"Layers"`
}
2021-05-24 00:11:57 +03:00
// Rootfs accepts a docker layer tarball and writes it to outfile.
2021-05-24 00:11:57 +03:00
// 1. create map[string]io.ReadSeeker for each layer.
// 2. parse manifest.json and get the layer order.
// 3. go through each layer in order and write:
// a) to an ordered slice: the file name.
// b) to an FS map: where does the file come from?
// I) layer name
// II) offset (0 being the first file in the layer)
// 4. go through
2021-05-24 00:11:57 +03:00
func RootFS(in io.ReadSeeker, out io.Writer) (err error) {
2021-05-24 00:11:57 +03:00
tr := tar.NewReader(in)
2021-05-24 00:11:57 +03:00
tw := tar.NewWriter(out)
2021-05-24 00:11:57 +03:00
defer func() {
err = multierr.Append(err, tw.Close())
}()
2021-05-24 00:11:57 +03:00
// layerOffsets maps a layer name (a9b123c0daa/layer.tar) to it's offset
2021-05-24 00:11:57 +03:00
layerOffsets := map[string]int64{}
2021-05-24 00:11:57 +03:00
// manifest is the docker manifest in the image
2021-05-24 00:11:57 +03:00
var manifest dockerManifestJSON
// phase 1: get layer offsets and manifest.json
for {
2021-05-24 00:11:57 +03:00
hdr, err := tr.Next()
2021-05-24 00:11:57 +03:00
if err == io.EOF {
break
}
2021-05-24 00:11:57 +03:00
if hdr.Typeflag != tar.TypeReg {
2021-05-24 00:11:57 +03:00
continue
}
switch {
2021-05-24 00:11:57 +03:00
case filepath.Clean(hdr.Name) == _manifestJSON:
2021-05-24 00:11:57 +03:00
dec := json.NewDecoder(tr)
if err := dec.Decode(&manifest); err != nil {
2021-05-24 00:11:57 +03:00
return err
2021-05-24 00:11:57 +03:00
}
2021-05-24 00:11:57 +03:00
case strings.HasSuffix(hdr.Name, "/layer.tar"):
2021-05-24 00:11:57 +03:00
here, err := in.Seek(0, io.SeekCurrent)
if err != nil {
2021-05-24 00:11:57 +03:00
return err
2021-05-24 00:11:57 +03:00
}
2021-05-24 00:11:57 +03:00
layerOffsets[hdr.Name] = here
2021-05-24 00:11:57 +03:00
}
}
2021-05-24 00:11:57 +03:00
if len(manifest) == 0 {
return ErrBadManifest
}
2021-05-24 00:11:58 +03:00
if len(layerOffsets) != len(manifest[0].Layers) {
return ErrBadManifest
}
2021-05-24 00:11:57 +03:00
// phase 1.5: enumerate layers
2021-05-24 00:11:57 +03:00
layers := make([]int64, len(layerOffsets))
for i, name := range manifest[0].Layers {
2021-05-24 00:11:57 +03:00
layers[i] = layerOffsets[name]
}
2021-05-24 00:11:57 +03:00
// file2layer maps a filename to layer number (index in "layers")
file2layer := map[string]int{}
// phase 2: iterate through all layers and save filenames
// for all kinds of files.
for i, offset := range layers {
if _, err := in.Seek(offset, io.SeekStart); err != nil {
2021-05-24 00:11:57 +03:00
return err
2021-05-24 00:11:57 +03:00
}
tr = tar.NewReader(in)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
2021-05-24 00:11:57 +03:00
if err != nil {
return err
}
2021-05-24 00:11:57 +03:00
file2layer[hdr.Name] = i
}
}
// phase 3: iterate through all layers and write files.
for i, offset := range layers {
if _, err := in.Seek(offset, io.SeekStart); err != nil {
2021-05-24 00:11:57 +03:00
return err
2021-05-24 00:11:57 +03:00
}
tr = tar.NewReader(in)
2021-05-24 00:11:57 +03:00
2021-05-24 00:11:57 +03:00
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
2021-05-24 00:11:57 +03:00
if err != nil {
return err
}
2021-05-24 00:11:58 +03:00
// only directories can have multiple entries with the same name.
// all other file types cannot.
if hdr.Typeflag != tar.TypeDir && file2layer[hdr.Name] != i {
2021-05-24 00:11:57 +03:00
continue
}
hdrOut := &tar.Header{
Typeflag: hdr.Typeflag,
Name: hdr.Name,
Linkname: hdr.Linkname,
Size: hdr.Size,
Mode: int64(hdr.Mode & 0777),
Uid: hdr.Uid,
Gid: hdr.Gid,
Uname: hdr.Uname,
Gname: hdr.Gname,
ModTime: hdr.ModTime,
Devmajor: hdr.Devmajor,
Devminor: hdr.Devminor,
2021-05-24 00:11:58 +03:00
Format: tar.FormatPAX,
2021-05-24 00:11:57 +03:00
}
if err := tw.WriteHeader(hdrOut); err != nil {
return err
}
if hdr.Typeflag == tar.TypeReg {
if _, err := io.Copy(tw, tr); err != nil {
return err
}
}
}
}
2021-05-24 00:11:57 +03:00
2021-05-24 00:11:57 +03:00
return nil
2021-05-24 00:11:57 +03:00
}