github.com/mgoltzsche/ctnr@v0.7.1-alpha/pkg/fs/source/sourcetar.go (about) 1 package source 2 3 import ( 4 "archive/tar" 5 "io" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/mgoltzsche/ctnr/pkg/fs" 11 "github.com/mgoltzsche/ctnr/pkg/idutils" 12 "github.com/openSUSE/umoci/oci/layer" 13 "github.com/openSUSE/umoci/pkg/system" 14 "github.com/opencontainers/go-digest" 15 "github.com/pkg/errors" 16 ) 17 18 var ( 19 dirAttrs = fs.FileAttrs{Mode: os.ModeDir | 0755} 20 _ fs.Source = &sourceTar{} 21 ) 22 23 type sourceTar struct { 24 file string 25 hash string 26 } 27 28 func NewSourceTar(file string) *sourceTar { 29 return &sourceTar{file, ""} 30 } 31 32 func (s *sourceTar) Attrs() fs.NodeInfo { 33 return fs.NodeInfo{fs.TypeOverlay, fs.FileAttrs{Mode: os.ModeDir | 0755}} 34 } 35 36 func (s *sourceTar) HashIfAvailable() string { 37 return s.hash 38 } 39 40 func (s *sourceTar) Hash() (string, error) { 41 if s.hash == "" { 42 f, err := os.Open(s.file) 43 if err != nil { 44 return "", errors.Errorf("hash: %s", err) 45 } 46 defer f.Close() 47 d, err := digest.FromReader(f) 48 if err != nil { 49 return "", errors.Errorf("hash %s: %s", s.file, err) 50 } 51 s.hash = d.String() 52 } 53 return s.hash, nil 54 } 55 56 func (s *sourceTar) DeriveAttrs() (a fs.DerivedAttrs, err error) { 57 a.Hash, err = s.Hash() 58 return 59 } 60 61 func (s *sourceTar) Write(dest, name string, w fs.Writer, written map[fs.Source]string) error { 62 return w.Lazy(dest, name, s, written) 63 } 64 65 func (s *sourceTar) Expand(dest string, w fs.Writer, written map[fs.Source]string) (err error) { 66 f, err := os.Open(s.file) 67 if err != nil { 68 return errors.Wrap(err, "extract tar") 69 } 70 defer f.Close() 71 if err = unpackTar(f, dest, w); err != nil { 72 return errors.Wrap(err, "extract tar") 73 } 74 return 75 } 76 77 func unpackTar(r io.Reader, dest string, w fs.Writer) error { 78 tr := tar.NewReader(r) 79 for { 80 hdr, err := tr.Next() 81 if err == io.EOF { 82 break 83 } 84 if err != nil { 85 return errors.Wrap(err, "read next tar entry") 86 } 87 links := map[string]string{} 88 if err = unpackTarEntry(hdr, tr, dest, w, links); err != nil { 89 return errors.Wrapf(err, "unpack tar entry: %s", hdr.Name) 90 } 91 for path, target := range links { 92 if err = w.Link(path, target); err != nil { 93 return errors.Wrapf(err, "unpack tar entry link: %s", hdr.Name) 94 } 95 } 96 } 97 return nil 98 } 99 100 // Derived from umoci's tar_extract.go to allow separate source/dest interfaces 101 // and filter archive contents on extraction 102 func unpackTarEntry(hdr *tar.Header, r io.Reader, dest string, w fs.Writer, links map[string]string) (err error) { 103 path := layer.CleanPath(filepath.Join(dest, hdr.Name)) 104 dir, file := filepath.Split(path) 105 106 // Remove file if whiteout 107 if strings.HasPrefix(file, fs.WhiteoutPrefix) { 108 file = strings.TrimPrefix(file, fs.WhiteoutPrefix) 109 return w.Remove(filepath.Join(dir, file)) 110 } 111 112 // Convert attributes 113 fi := hdr.FileInfo() 114 a := fs.FileAttrs{ 115 Mode: fi.Mode() | os.FileMode(system.Tarmode(hdr.Typeflag)), 116 UserIds: idutils.UserIds{uint(hdr.Uid), uint(hdr.Gid)}, 117 FileTimes: fs.FileTimes{ 118 Atime: hdr.AccessTime, 119 Mtime: hdr.ModTime, 120 }, 121 Xattrs: hdr.Xattrs, 122 } 123 124 // Write file 125 switch hdr.Typeflag { 126 // regular file 127 case tar.TypeReg, tar.TypeRegA: 128 delete(links, path) 129 a.Size = hdr.Size 130 _, err = w.File(path, NewSourceFile(fs.NewReadable(r), a)) 131 // directory 132 case tar.TypeDir: 133 delete(links, path) 134 err = w.Dir(path, filepath.Base(path), a) 135 // hard link 136 case tar.TypeLink: 137 links[path] = filepath.Join(string(filepath.Separator)+dest, hdr.Linkname) 138 // symbolic link 139 case tar.TypeSymlink: 140 a.Symlink = hdr.Linkname 141 if filepath.IsAbs(a.Symlink) { 142 a.Symlink = filepath.Join(string(filepath.Separator)+dest, a.Symlink) 143 } 144 delete(links, path) 145 err = w.Symlink(path, a) 146 // character device node, block device node 147 case tar.TypeChar, tar.TypeBlock: 148 delete(links, path) 149 err = w.Device(path, fs.DeviceAttrs{a, hdr.Devmajor, hdr.Devminor}) 150 // fifo node 151 case tar.TypeFifo: 152 delete(links, path) 153 err = w.Fifo(path, fs.DeviceAttrs{a, hdr.Devmajor, hdr.Devminor}) 154 default: 155 err = errors.Errorf("unpack entry: %s: unknown typeflag '\\x%x'", hdr.Name, hdr.Typeflag) 156 } 157 return 158 }