The Go Programming Language

Source file src/pkg/archive/tar/writer.go

     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	// TODO(dsymonds):
     8	// - catch more errors (no first header, write after close, etc.)
     9	
    10	import (
    11		"io"
    12		"os"
    13		"strconv"
    14	)
    15	
    16	var (
    17		ErrWriteTooLong    = os.NewError("write too long")
    18		ErrFieldTooLong    = os.NewError("header field too long")
    19		ErrWriteAfterClose = os.NewError("write after close")
    20	)
    21	
    22	// A Writer provides sequential writing of a tar archive in POSIX.1 format.
    23	// A tar archive consists of a sequence of files.
    24	// Call WriteHeader to begin a new file, and then call Write to supply that file's data,
    25	// writing at most hdr.Size bytes in total.
    26	//
    27	// Example:
    28	//	tw := tar.NewWriter(w)
    29	//	hdr := new(Header)
    30	//	hdr.Size = length of data in bytes
    31	//	// populate other hdr fields as desired
    32	//	if err := tw.WriteHeader(hdr); err != nil {
    33	//		// handle error
    34	//	}
    35	//	io.Copy(tw, data)
    36	//	tw.Close()
    37	type Writer struct {
    38		w          io.Writer
    39		err        os.Error
    40		nb         int64 // number of unwritten bytes for current file entry
    41		pad        int64 // amount of padding to write after current file entry
    42		closed     bool
    43		usedBinary bool // whether the binary numeric field extension was used
    44	}
    45	
    46	// NewWriter creates a new Writer writing to w.
    47	func NewWriter(w io.Writer) *Writer { return &Writer{w: w} }
    48	
    49	// Flush finishes writing the current file (optional).
    50	func (tw *Writer) Flush() os.Error {
    51		n := tw.nb + tw.pad
    52		for n > 0 && tw.err == nil {
    53			nr := n
    54			if nr > blockSize {
    55				nr = blockSize
    56			}
    57			var nw int
    58			nw, tw.err = tw.w.Write(zeroBlock[0:nr])
    59			n -= int64(nw)
    60		}
    61		tw.nb = 0
    62		tw.pad = 0
    63		return tw.err
    64	}
    65	
    66	// Write s into b, terminating it with a NUL if there is room.
    67	func (tw *Writer) cString(b []byte, s string) {
    68		if len(s) > len(b) {
    69			if tw.err == nil {
    70				tw.err = ErrFieldTooLong
    71			}
    72			return
    73		}
    74		copy(b, s)
    75		if len(s) < len(b) {
    76			b[len(s)] = 0
    77		}
    78	}
    79	
    80	// Encode x as an octal ASCII string and write it into b with leading zeros.
    81	func (tw *Writer) octal(b []byte, x int64) {
    82		s := strconv.Itob64(x, 8)
    83		// leading zeros, but leave room for a NUL.
    84		for len(s)+1 < len(b) {
    85			s = "0" + s
    86		}
    87		tw.cString(b, s)
    88	}
    89	
    90	// Write x into b, either as octal or as binary (GNUtar/star extension).
    91	func (tw *Writer) numeric(b []byte, x int64) {
    92		// Try octal first.
    93		s := strconv.Itob64(x, 8)
    94		if len(s) < len(b) {
    95			tw.octal(b, x)
    96			return
    97		}
    98		// Too big: use binary (big-endian).
    99		tw.usedBinary = true
   100		for i := len(b) - 1; x > 0 && i >= 0; i-- {
   101			b[i] = byte(x)
   102			x >>= 8
   103		}
   104		b[0] |= 0x80 // highest bit indicates binary format
   105	}
   106	
   107	// WriteHeader writes hdr and prepares to accept the file's contents.
   108	// WriteHeader calls Flush if it is not the first header.
   109	// Calling after a Close will return ErrWriteAfterClose.
   110	func (tw *Writer) WriteHeader(hdr *Header) os.Error {
   111		if tw.closed {
   112			return ErrWriteAfterClose
   113		}
   114		if tw.err == nil {
   115			tw.Flush()
   116		}
   117		if tw.err != nil {
   118			return tw.err
   119		}
   120	
   121		tw.nb = int64(hdr.Size)
   122		tw.pad = -tw.nb & (blockSize - 1) // blockSize is a power of two
   123	
   124		header := make([]byte, blockSize)
   125		s := slicer(header)
   126	
   127		// TODO(dsymonds): handle names longer than 100 chars
   128		copy(s.next(100), []byte(hdr.Name))
   129	
   130		tw.octal(s.next(8), hdr.Mode)          // 100:108
   131		tw.numeric(s.next(8), int64(hdr.Uid))  // 108:116
   132		tw.numeric(s.next(8), int64(hdr.Gid))  // 116:124
   133		tw.numeric(s.next(12), hdr.Size)       // 124:136
   134		tw.numeric(s.next(12), hdr.Mtime)      // 136:148
   135		s.next(8)                              // chksum (148:156)
   136		s.next(1)[0] = hdr.Typeflag            // 156:157
   137		s.next(100)                            // linkname (157:257)
   138		copy(s.next(8), []byte("ustar\x0000")) // 257:265
   139		tw.cString(s.next(32), hdr.Uname)      // 265:297
   140		tw.cString(s.next(32), hdr.Gname)      // 297:329
   141		tw.numeric(s.next(8), hdr.Devmajor)    // 329:337
   142		tw.numeric(s.next(8), hdr.Devminor)    // 337:345
   143	
   144		// Use the GNU magic instead of POSIX magic if we used any GNU extensions.
   145		if tw.usedBinary {
   146			copy(header[257:265], []byte("ustar  \x00"))
   147		}
   148	
   149		// The chksum field is terminated by a NUL and a space.
   150		// This is different from the other octal fields.
   151		chksum, _ := checksum(header)
   152		tw.octal(header[148:155], chksum)
   153		header[155] = ' '
   154	
   155		if tw.err != nil {
   156			// problem with header; probably integer too big for a field.
   157			return tw.err
   158		}
   159	
   160		_, tw.err = tw.w.Write(header)
   161	
   162		return tw.err
   163	}
   164	
   165	// Write writes to the current entry in the tar archive.
   166	// Write returns the error ErrWriteTooLong if more than
   167	// hdr.Size bytes are written after WriteHeader.
   168	func (tw *Writer) Write(b []byte) (n int, err os.Error) {
   169		if tw.closed {
   170			err = ErrWriteTooLong
   171			return
   172		}
   173		overwrite := false
   174		if int64(len(b)) > tw.nb {
   175			b = b[0:tw.nb]
   176			overwrite = true
   177		}
   178		n, err = tw.w.Write(b)
   179		tw.nb -= int64(n)
   180		if err == nil && overwrite {
   181			err = ErrWriteTooLong
   182			return
   183		}
   184		tw.err = err
   185		return
   186	}
   187	
   188	// Close closes the tar archive, flushing any unwritten
   189	// data to the underlying writer.
   190	func (tw *Writer) Close() os.Error {
   191		if tw.err != nil || tw.closed {
   192			return tw.err
   193		}
   194		tw.Flush()
   195		tw.closed = true
   196	
   197		// trailer: two zero blocks
   198		for i := 0; i < 2; i++ {
   199			_, tw.err = tw.w.Write(zeroBlock)
   200			if tw.err != nil {
   201				break
   202			}
   203		}
   204		return tw.err
   205	}

release.r60.3. Except as noted, this content is licensed under a Creative Commons Attribution 3.0 License.