package archive import ( "archive/tar" "archive/zip" "fmt" "io" "io/ioutil" "os" "time" "github.com/cihub/seelog/archive/gzip" ) // Reader is the interface for reading files from an archive. type Reader interface { NextFile() (name string, err error) io.Reader } // ReadCloser is the interface that groups Reader with the Close method. type ReadCloser interface { Reader io.Closer } // Writer is the interface for writing files to an archived format. type Writer interface { NextFile(name string, fi os.FileInfo) error io.Writer } // WriteCloser is the interface that groups Writer with the Close method. type WriteCloser interface { Writer io.Closer } type nopCloser struct{ Reader } func (nopCloser) Close() error { return nil } // NopCloser returns a ReadCloser with a no-op Close method wrapping the // provided Reader r. func NopCloser(r Reader) ReadCloser { return nopCloser{r} } // Copy copies from src to dest until either EOF is reached on src or an error // occurs. // // When the archive format of src matches that of dst, Copy streams the files // directly into dst. Otherwise, copy buffers the contents to disk to compute // headers before writing to dst. func Copy(dst Writer, src Reader) error { switch src := src.(type) { case tarReader: if dst, ok := dst.(tarWriter); ok { return copyTar(dst, src) } case zipReader: if dst, ok := dst.(zipWriter); ok { return copyZip(dst, src) } // Switch on concrete type because gzip has no special methods case *gzip.Reader: if dst, ok := dst.(*gzip.Writer); ok { _, err := io.Copy(dst, src) return err } } return copyBuffer(dst, src) } func copyBuffer(dst Writer, src Reader) (err error) { const defaultFileMode = 0666 buf, err := ioutil.TempFile("", "archive_copy_buffer") if err != nil { return err } defer os.Remove(buf.Name()) // Do not care about failure removing temp defer buf.Close() // Do not care about failure closing temp for { // Handle the next file name, err := src.NextFile() switch err { case io.EOF: // Done copying return nil default: // Failed to write: bail out return err case nil: // Proceed below } // Buffer the file if _, err := io.Copy(buf, src); err != nil { return fmt.Errorf("buffer to disk: %v", err) } // Seek to the start of the file for full file copy if _, err := buf.Seek(0, os.SEEK_SET); err != nil { return err } // Set desired file permissions if err := os.Chmod(buf.Name(), defaultFileMode); err != nil { return err } fi, err := buf.Stat() if err != nil { return err } // Write the buffered file if err := dst.NextFile(name, fi); err != nil { return err } if _, err := io.Copy(dst, buf); err != nil { return fmt.Errorf("copy to dst: %v", err) } if err := buf.Truncate(0); err != nil { return err } if _, err := buf.Seek(0, os.SEEK_SET); err != nil { return err } } } type tarReader interface { Next() (*tar.Header, error) io.Reader } type tarWriter interface { WriteHeader(hdr *tar.Header) error io.Writer } type zipReader interface { Files() []*zip.File } type zipWriter interface { CreateHeader(fh *zip.FileHeader) (io.Writer, error) } func copyTar(w tarWriter, r tarReader) error { for { hdr, err := r.Next() switch err { case io.EOF: return nil default: // Handle error return err case nil: // Proceed below } info := hdr.FileInfo() // Skip directories if info.IsDir() { continue } if err := w.WriteHeader(hdr); err != nil { return err } if _, err := io.Copy(w, r); err != nil { return err } } } func copyZip(zw zipWriter, r zipReader) error { for _, f := range r.Files() { if err := copyZipFile(zw, f); err != nil { return err } } return nil } func copyZipFile(zw zipWriter, f *zip.File) error { rc, err := f.Open() if err != nil { return err } defer rc.Close() // Read-only hdr := f.FileHeader hdr.SetModTime(time.Now()) w, err := zw.CreateHeader(&hdr) if err != nil { return err } _, err = io.Copy(w, rc) return err }