github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/archive/tartest/tar.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package tartest 18 19 import ( 20 "archive/tar" 21 "errors" 22 "io" 23 "os" 24 "time" 25 ) 26 27 // WriterToTar is an type which writes to a tar writer 28 type WriterToTar interface { 29 WriteTo(*tar.Writer) error 30 } 31 32 type writerToFn func(*tar.Writer) error 33 34 func (w writerToFn) WriteTo(tw *tar.Writer) error { 35 return w(tw) 36 } 37 38 // TarAll creates a WriterToTar which calls all the provided writers 39 // in the order in which they are provided. 40 func TarAll(wt ...WriterToTar) WriterToTar { 41 return writerToFn(func(tw *tar.Writer) error { 42 for _, w := range wt { 43 if err := w.WriteTo(tw); err != nil { 44 return err 45 } 46 } 47 return nil 48 }) 49 } 50 51 // TarFromWriterTo is used to create a tar stream from a tar record 52 // creator. This can be used to manufacture more specific tar records 53 // which allow testing specific tar cases which may be encountered 54 // by the untar process. 55 func TarFromWriterTo(wt WriterToTar) io.ReadCloser { 56 r, w := io.Pipe() 57 go func() { 58 tw := tar.NewWriter(w) 59 if err := wt.WriteTo(tw); err != nil { 60 w.CloseWithError(err) 61 return 62 } 63 w.CloseWithError(tw.Close()) 64 }() 65 66 return r 67 } 68 69 // TarContext is used to create tar records 70 type TarContext struct { 71 UID int 72 GID int 73 74 // ModTime sets the modtimes for all files, if nil the current time 75 // is used for each file when it was written 76 ModTime *time.Time 77 78 Xattrs map[string]string 79 } 80 81 func (tc TarContext) newHeader(mode os.FileMode, name, link string, size int64) *tar.Header { 82 ti := tarInfo{ 83 name: name, 84 mode: mode, 85 size: size, 86 modt: tc.ModTime, 87 hdr: &tar.Header{ 88 Uid: tc.UID, 89 Gid: tc.GID, 90 Xattrs: tc.Xattrs, 91 }, 92 } 93 94 if mode&os.ModeSymlink == 0 && link != "" { 95 ti.hdr.Typeflag = tar.TypeLink 96 ti.hdr.Linkname = link 97 } 98 99 hdr, err := tar.FileInfoHeader(ti, link) 100 if err != nil { 101 // Only returns an error on bad input mode 102 panic(err) 103 } 104 105 return hdr 106 } 107 108 type tarInfo struct { 109 name string 110 mode os.FileMode 111 size int64 112 modt *time.Time 113 hdr *tar.Header 114 } 115 116 func (ti tarInfo) Name() string { 117 return ti.name 118 } 119 120 func (ti tarInfo) Size() int64 { 121 return ti.size 122 } 123 func (ti tarInfo) Mode() os.FileMode { 124 return ti.mode 125 } 126 127 func (ti tarInfo) ModTime() time.Time { 128 if ti.modt != nil { 129 return *ti.modt 130 } 131 return time.Now().UTC() 132 } 133 134 func (ti tarInfo) IsDir() bool { 135 return (ti.mode & os.ModeDir) != 0 136 } 137 func (ti tarInfo) Sys() interface{} { 138 return ti.hdr 139 } 140 141 // WithUIDGID sets the UID and GID for tar entries 142 func (tc TarContext) WithUIDGID(uid, gid int) TarContext { 143 ntc := tc 144 ntc.UID = uid 145 ntc.GID = gid 146 return ntc 147 } 148 149 // WithModTime sets the ModTime for tar entries 150 func (tc TarContext) WithModTime(modtime time.Time) TarContext { 151 ntc := tc 152 ntc.ModTime = &modtime 153 return ntc 154 } 155 156 // WithXattrs adds these xattrs to all files, merges with any 157 // previously added xattrs 158 func (tc TarContext) WithXattrs(xattrs map[string]string) TarContext { 159 ntc := tc 160 if ntc.Xattrs == nil { 161 ntc.Xattrs = map[string]string{} 162 } 163 for k, v := range xattrs { 164 ntc.Xattrs[k] = v 165 } 166 return ntc 167 } 168 169 // File returns a regular file tar entry using the provided bytes 170 func (tc TarContext) File(name string, content []byte, perm os.FileMode) WriterToTar { 171 return writerToFn(func(tw *tar.Writer) error { 172 return writeHeaderAndContent(tw, tc.newHeader(perm, name, "", int64(len(content))), content) 173 }) 174 } 175 176 // Dir returns a directory tar entry 177 func (tc TarContext) Dir(name string, perm os.FileMode) WriterToTar { 178 return writerToFn(func(tw *tar.Writer) error { 179 return writeHeaderAndContent(tw, tc.newHeader(perm|os.ModeDir, name, "", 0), nil) 180 }) 181 } 182 183 // Symlink returns a symlink tar entry 184 func (tc TarContext) Symlink(oldname, newname string) WriterToTar { 185 return writerToFn(func(tw *tar.Writer) error { 186 return writeHeaderAndContent(tw, tc.newHeader(0777|os.ModeSymlink, newname, oldname, 0), nil) 187 }) 188 } 189 190 // Link returns a hard link tar entry 191 func (tc TarContext) Link(oldname, newname string) WriterToTar { 192 return writerToFn(func(tw *tar.Writer) error { 193 return writeHeaderAndContent(tw, tc.newHeader(0777, newname, oldname, 0), nil) 194 }) 195 } 196 197 func writeHeaderAndContent(tw *tar.Writer, h *tar.Header, b []byte) error { 198 if h.Size != int64(len(b)) { 199 return errors.New("bad content length") 200 } 201 if err := tw.WriteHeader(h); err != nil { 202 return err 203 } 204 if len(b) > 0 { 205 if _, err := tw.Write(b); err != nil { 206 return err 207 } 208 } 209 return nil 210 }