github.com/karrick/go@v0.0.0-20170817181416-d5b0ec858b37/src/archive/tar/writer.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package tar 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "path" 12 "sort" 13 "strings" 14 "time" 15 ) 16 17 // A Writer provides sequential writing of a tar archive in POSIX.1 format. 18 // A tar archive consists of a sequence of files. 19 // Call WriteHeader to begin a new file, and then call Write to supply that file's data, 20 // writing at most hdr.Size bytes in total. 21 type Writer struct { 22 w io.Writer 23 nb int64 // number of unwritten bytes for current file entry 24 pad int64 // amount of padding to write after current file entry 25 hdr Header // Shallow copy of Header that is safe for mutations 26 blk block // Buffer to use as temporary local storage 27 28 // err is a persistent error. 29 // It is only the responsibility of every exported method of Writer to 30 // ensure that this error is sticky. 31 err error 32 } 33 34 // NewWriter creates a new Writer writing to w. 35 func NewWriter(w io.Writer) *Writer { return &Writer{w: w} } 36 37 // Flush finishes writing the current file's block padding. 38 // The current file must be fully written before Flush can be called. 39 // 40 // Deprecated: This is unecessary as the next call to WriteHeader or Close 41 // will implicitly flush out the file's padding. 42 func (tw *Writer) Flush() error { 43 if tw.err != nil { 44 return tw.err 45 } 46 if tw.nb > 0 { 47 return fmt.Errorf("archive/tar: missed writing %d bytes", tw.nb) 48 } 49 if _, tw.err = tw.w.Write(zeroBlock[:tw.pad]); tw.err != nil { 50 return tw.err 51 } 52 tw.pad = 0 53 return nil 54 } 55 56 // WriteHeader writes hdr and prepares to accept the file's contents. 57 // WriteHeader calls Flush if it is not the first header. 58 // Calling after a Close will return ErrWriteAfterClose. 59 func (tw *Writer) WriteHeader(hdr *Header) error { 60 if err := tw.Flush(); err != nil { 61 return err 62 } 63 64 tw.hdr = *hdr // Shallow copy of Header 65 switch allowedFormats, paxHdrs := tw.hdr.allowedFormats(); { 66 case allowedFormats&formatUSTAR != 0: 67 tw.err = tw.writeUSTARHeader(&tw.hdr) 68 return tw.err 69 case allowedFormats&formatPAX != 0: 70 tw.err = tw.writePAXHeader(&tw.hdr, paxHdrs) 71 return tw.err 72 case allowedFormats&formatGNU != 0: 73 tw.err = tw.writeGNUHeader(&tw.hdr) 74 return tw.err 75 default: 76 return ErrHeader // Non-fatal error 77 } 78 } 79 80 func (tw *Writer) writeUSTARHeader(hdr *Header) error { 81 // Check if we can use USTAR prefix/suffix splitting. 82 var namePrefix string 83 if prefix, suffix, ok := splitUSTARPath(hdr.Name); ok { 84 namePrefix, hdr.Name = prefix, suffix 85 } 86 87 // Pack the main header. 88 var f formatter 89 blk := tw.templateV7Plus(hdr, f.formatString, f.formatOctal) 90 f.formatString(blk.USTAR().Prefix(), namePrefix) 91 blk.SetFormat(formatUSTAR) 92 if f.err != nil { 93 return f.err // Should never happen since header is validated 94 } 95 return tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag) 96 } 97 98 func (tw *Writer) writePAXHeader(hdr *Header, paxHdrs map[string]string) error { 99 // Write PAX records to the output. 100 if len(paxHdrs) > 0 { 101 // Sort keys for deterministic ordering. 102 var keys []string 103 for k := range paxHdrs { 104 keys = append(keys, k) 105 } 106 sort.Strings(keys) 107 108 // Write each record to a buffer. 109 var buf bytes.Buffer 110 for _, k := range keys { 111 rec, err := formatPAXRecord(k, paxHdrs[k]) 112 if err != nil { 113 return err 114 } 115 buf.WriteString(rec) 116 } 117 118 // Write the extended header file. 119 dir, file := path.Split(hdr.Name) 120 name := path.Join(dir, "PaxHeaders.0", file) 121 data := buf.String() 122 if err := tw.writeRawFile(name, data, TypeXHeader, formatPAX); err != nil { 123 return err 124 } 125 } 126 127 // Pack the main header. 128 var f formatter // Ignore errors since they are expected 129 fmtStr := func(b []byte, s string) { f.formatString(b, toASCII(s)) } 130 blk := tw.templateV7Plus(hdr, fmtStr, f.formatOctal) 131 blk.SetFormat(formatPAX) 132 return tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag) 133 } 134 135 func (tw *Writer) writeGNUHeader(hdr *Header) error { 136 // TODO(dsnet): Support writing sparse files. 137 // See https://golang.org/issue/13548 138 139 // Use long-link files if Name or Linkname exceeds the field size. 140 const longName = "././@LongLink" 141 if len(hdr.Name) > nameSize { 142 data := hdr.Name + "\x00" 143 if err := tw.writeRawFile(longName, data, TypeGNULongName, formatGNU); err != nil { 144 return err 145 } 146 } 147 if len(hdr.Linkname) > nameSize { 148 data := hdr.Linkname + "\x00" 149 if err := tw.writeRawFile(longName, data, TypeGNULongLink, formatGNU); err != nil { 150 return err 151 } 152 } 153 154 // Pack the main header. 155 var f formatter // Ignore errors since they are expected 156 blk := tw.templateV7Plus(hdr, f.formatString, f.formatNumeric) 157 if !hdr.AccessTime.IsZero() { 158 f.formatNumeric(blk.GNU().AccessTime(), hdr.AccessTime.Unix()) 159 } 160 if !hdr.ChangeTime.IsZero() { 161 f.formatNumeric(blk.GNU().ChangeTime(), hdr.ChangeTime.Unix()) 162 } 163 blk.SetFormat(formatGNU) 164 return tw.writeRawHeader(blk, hdr.Size, hdr.Typeflag) 165 } 166 167 type ( 168 stringFormatter func([]byte, string) 169 numberFormatter func([]byte, int64) 170 ) 171 172 // templateV7Plus fills out the V7 fields of a block using values from hdr. 173 // It also fills out fields (uname, gname, devmajor, devminor) that are 174 // shared in the USTAR, PAX, and GNU formats using the provided formatters. 175 // 176 // The block returned is only valid until the next call to 177 // templateV7Plus or writeRawFile. 178 func (tw *Writer) templateV7Plus(hdr *Header, fmtStr stringFormatter, fmtNum numberFormatter) *block { 179 tw.blk.Reset() 180 181 modTime := hdr.ModTime 182 if modTime.IsZero() { 183 modTime = time.Unix(0, 0) 184 } 185 186 v7 := tw.blk.V7() 187 v7.TypeFlag()[0] = hdr.Typeflag 188 fmtStr(v7.Name(), hdr.Name) 189 fmtStr(v7.LinkName(), hdr.Linkname) 190 fmtNum(v7.Mode(), hdr.Mode) 191 fmtNum(v7.UID(), int64(hdr.Uid)) 192 fmtNum(v7.GID(), int64(hdr.Gid)) 193 fmtNum(v7.Size(), hdr.Size) 194 fmtNum(v7.ModTime(), modTime.Unix()) 195 196 ustar := tw.blk.USTAR() 197 fmtStr(ustar.UserName(), hdr.Uname) 198 fmtStr(ustar.GroupName(), hdr.Gname) 199 fmtNum(ustar.DevMajor(), hdr.Devmajor) 200 fmtNum(ustar.DevMinor(), hdr.Devminor) 201 202 return &tw.blk 203 } 204 205 // writeRawFile writes a minimal file with the given name and flag type. 206 // It uses format to encode the header format and will write data as the body. 207 // It uses default values for all of the other fields (as BSD and GNU tar does). 208 func (tw *Writer) writeRawFile(name, data string, flag byte, format int) error { 209 tw.blk.Reset() 210 211 // Best effort for the filename. 212 name = toASCII(name) 213 if len(name) > nameSize { 214 name = name[:nameSize] 215 } 216 217 var f formatter 218 v7 := tw.blk.V7() 219 v7.TypeFlag()[0] = flag 220 f.formatString(v7.Name(), name) 221 f.formatOctal(v7.Mode(), 0) 222 f.formatOctal(v7.UID(), 0) 223 f.formatOctal(v7.GID(), 0) 224 f.formatOctal(v7.Size(), int64(len(data))) // Must be < 8GiB 225 f.formatOctal(v7.ModTime(), 0) 226 tw.blk.SetFormat(format) 227 if f.err != nil { 228 return f.err // Only occurs if size condition is violated 229 } 230 231 // Write the header and data. 232 if err := tw.writeRawHeader(&tw.blk, int64(len(data)), flag); err != nil { 233 return err 234 } 235 _, err := io.WriteString(tw, data) 236 return err 237 } 238 239 // writeRawHeader writes the value of blk, regardless of its value. 240 // It sets up the Writer such that it can accept a file of the given size. 241 // If the flag is a special header-only flag, then the size is treated as zero. 242 func (tw *Writer) writeRawHeader(blk *block, size int64, flag byte) error { 243 if err := tw.Flush(); err != nil { 244 return err 245 } 246 if _, err := tw.w.Write(blk[:]); err != nil { 247 return err 248 } 249 if isHeaderOnlyType(flag) { 250 size = 0 251 } 252 tw.nb = size 253 tw.pad = -size & (blockSize - 1) // blockSize is a power of two 254 return nil 255 } 256 257 // splitUSTARPath splits a path according to USTAR prefix and suffix rules. 258 // If the path is not splittable, then it will return ("", "", false). 259 func splitUSTARPath(name string) (prefix, suffix string, ok bool) { 260 length := len(name) 261 if length <= nameSize || !isASCII(name) { 262 return "", "", false 263 } else if length > prefixSize+1 { 264 length = prefixSize + 1 265 } else if name[length-1] == '/' { 266 length-- 267 } 268 269 i := strings.LastIndex(name[:length], "/") 270 nlen := len(name) - i - 1 // nlen is length of suffix 271 plen := i // plen is length of prefix 272 if i <= 0 || nlen > nameSize || nlen == 0 || plen > prefixSize { 273 return "", "", false 274 } 275 return name[:i], name[i+1:], true 276 } 277 278 // Write writes to the current entry in the tar archive. 279 // Write returns the error ErrWriteTooLong if more than 280 // Header.Size bytes are written after WriteHeader. 281 // 282 // Calling Write on special types like TypeLink, TypeSymLink, TypeChar, 283 // TypeBlock, TypeDir, and TypeFifo returns (0, ErrWriteTooLong) regardless 284 // of what the Header.Size claims. 285 func (tw *Writer) Write(b []byte) (int, error) { 286 if tw.err != nil { 287 return 0, tw.err 288 } 289 290 overwrite := int64(len(b)) > tw.nb 291 if overwrite { 292 b = b[:tw.nb] 293 } 294 n, err := tw.w.Write(b) 295 tw.nb -= int64(n) 296 if err == nil && overwrite { 297 return n, ErrWriteTooLong // Non-fatal error 298 } 299 tw.err = err 300 return n, err 301 } 302 303 // Close closes the tar archive, flushing any unwritten 304 // data to the underlying writer. 305 func (tw *Writer) Close() error { 306 if tw.err == ErrWriteAfterClose { 307 return nil 308 } 309 if tw.err != nil { 310 return tw.err 311 } 312 313 // Trailer: two zero blocks. 314 err := tw.Flush() 315 for i := 0; i < 2 && err == nil; i++ { 316 _, err = tw.w.Write(zeroBlock[:]) 317 } 318 319 // Ensure all future actions are invalid. 320 tw.err = ErrWriteAfterClose 321 return err // Report IO errors 322 }