400 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			400 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
// Copyright 2014-2021 Ulrich Kunitz. All rights reserved.
 | 
						|
// Use of this source code is governed by a BSD-style
 | 
						|
// license that can be found in the LICENSE file.
 | 
						|
 | 
						|
package xz
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"hash"
 | 
						|
	"io"
 | 
						|
 | 
						|
	"github.com/ulikunitz/xz/lzma"
 | 
						|
)
 | 
						|
 | 
						|
// WriterConfig describe the parameters for an xz writer.
 | 
						|
type WriterConfig struct {
 | 
						|
	Properties *lzma.Properties
 | 
						|
	DictCap    int
 | 
						|
	BufSize    int
 | 
						|
	BlockSize  int64
 | 
						|
	// checksum method: CRC32, CRC64 or SHA256 (default: CRC64)
 | 
						|
	CheckSum byte
 | 
						|
	// Forces NoChecksum (default: false)
 | 
						|
	NoCheckSum bool
 | 
						|
	// match algorithm
 | 
						|
	Matcher lzma.MatchAlgorithm
 | 
						|
}
 | 
						|
 | 
						|
// fill replaces zero values with default values.
 | 
						|
func (c *WriterConfig) fill() {
 | 
						|
	if c.Properties == nil {
 | 
						|
		c.Properties = &lzma.Properties{LC: 3, LP: 0, PB: 2}
 | 
						|
	}
 | 
						|
	if c.DictCap == 0 {
 | 
						|
		c.DictCap = 8 * 1024 * 1024
 | 
						|
	}
 | 
						|
	if c.BufSize == 0 {
 | 
						|
		c.BufSize = 4096
 | 
						|
	}
 | 
						|
	if c.BlockSize == 0 {
 | 
						|
		c.BlockSize = maxInt64
 | 
						|
	}
 | 
						|
	if c.CheckSum == 0 {
 | 
						|
		c.CheckSum = CRC64
 | 
						|
	}
 | 
						|
	if c.NoCheckSum {
 | 
						|
		c.CheckSum = None
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Verify checks the configuration for errors. Zero values will be
 | 
						|
// replaced by default values.
 | 
						|
func (c *WriterConfig) Verify() error {
 | 
						|
	if c == nil {
 | 
						|
		return errors.New("xz: writer configuration is nil")
 | 
						|
	}
 | 
						|
	c.fill()
 | 
						|
	lc := lzma.Writer2Config{
 | 
						|
		Properties: c.Properties,
 | 
						|
		DictCap:    c.DictCap,
 | 
						|
		BufSize:    c.BufSize,
 | 
						|
		Matcher:    c.Matcher,
 | 
						|
	}
 | 
						|
	if err := lc.Verify(); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if c.BlockSize <= 0 {
 | 
						|
		return errors.New("xz: block size out of range")
 | 
						|
	}
 | 
						|
	if err := verifyFlags(c.CheckSum); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// filters creates the filter list for the given parameters.
 | 
						|
func (c *WriterConfig) filters() []filter {
 | 
						|
	return []filter{&lzmaFilter{int64(c.DictCap)}}
 | 
						|
}
 | 
						|
 | 
						|
// maxInt64 defines the maximum 64-bit signed integer.
 | 
						|
const maxInt64 = 1<<63 - 1
 | 
						|
 | 
						|
// verifyFilters checks the filter list for the length and the right
 | 
						|
// sequence of filters.
 | 
						|
func verifyFilters(f []filter) error {
 | 
						|
	if len(f) == 0 {
 | 
						|
		return errors.New("xz: no filters")
 | 
						|
	}
 | 
						|
	if len(f) > 4 {
 | 
						|
		return errors.New("xz: more than four filters")
 | 
						|
	}
 | 
						|
	for _, g := range f[:len(f)-1] {
 | 
						|
		if g.last() {
 | 
						|
			return errors.New("xz: last filter is not last")
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if !f[len(f)-1].last() {
 | 
						|
		return errors.New("xz: wrong last filter")
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// newFilterWriteCloser converts a filter list into a WriteCloser that
 | 
						|
// can be used by a blockWriter.
 | 
						|
func (c *WriterConfig) newFilterWriteCloser(w io.Writer, f []filter) (fw io.WriteCloser, err error) {
 | 
						|
	if err = verifyFilters(f); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	fw = nopWriteCloser(w)
 | 
						|
	for i := len(f) - 1; i >= 0; i-- {
 | 
						|
		fw, err = f[i].writeCloser(fw, c)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return fw, nil
 | 
						|
}
 | 
						|
 | 
						|
// nopWCloser implements a WriteCloser with a Close method not doing
 | 
						|
// anything.
 | 
						|
type nopWCloser struct {
 | 
						|
	io.Writer
 | 
						|
}
 | 
						|
 | 
						|
// Close returns nil and doesn't do anything else.
 | 
						|
func (c nopWCloser) Close() error {
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// nopWriteCloser converts the Writer into a WriteCloser with a Close
 | 
						|
// function that does nothing beside returning nil.
 | 
						|
func nopWriteCloser(w io.Writer) io.WriteCloser {
 | 
						|
	return nopWCloser{w}
 | 
						|
}
 | 
						|
 | 
						|
// Writer compresses data written to it. It is an io.WriteCloser.
 | 
						|
type Writer struct {
 | 
						|
	WriterConfig
 | 
						|
 | 
						|
	xz      io.Writer
 | 
						|
	bw      *blockWriter
 | 
						|
	newHash func() hash.Hash
 | 
						|
	h       header
 | 
						|
	index   []record
 | 
						|
	closed  bool
 | 
						|
}
 | 
						|
 | 
						|
// newBlockWriter creates a new block writer writes the header out.
 | 
						|
func (w *Writer) newBlockWriter() error {
 | 
						|
	var err error
 | 
						|
	w.bw, err = w.WriterConfig.newBlockWriter(w.xz, w.newHash())
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if err = w.bw.writeHeader(w.xz); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// closeBlockWriter closes a block writer and records the sizes in the
 | 
						|
// index.
 | 
						|
func (w *Writer) closeBlockWriter() error {
 | 
						|
	var err error
 | 
						|
	if err = w.bw.Close(); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	w.index = append(w.index, w.bw.record())
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// NewWriter creates a new xz writer using default parameters.
 | 
						|
func NewWriter(xz io.Writer) (w *Writer, err error) {
 | 
						|
	return WriterConfig{}.NewWriter(xz)
 | 
						|
}
 | 
						|
 | 
						|
// NewWriter creates a new Writer using the given configuration parameters.
 | 
						|
func (c WriterConfig) NewWriter(xz io.Writer) (w *Writer, err error) {
 | 
						|
	if err = c.Verify(); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	w = &Writer{
 | 
						|
		WriterConfig: c,
 | 
						|
		xz:           xz,
 | 
						|
		h:            header{c.CheckSum},
 | 
						|
		index:        make([]record, 0, 4),
 | 
						|
	}
 | 
						|
	if w.newHash, err = newHashFunc(c.CheckSum); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	data, err := w.h.MarshalBinary()
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("w.h.MarshalBinary(): error %w", err)
 | 
						|
	}
 | 
						|
	if _, err = xz.Write(data); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if err = w.newBlockWriter(); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return w, nil
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
// Write compresses the uncompressed data provided.
 | 
						|
func (w *Writer) Write(p []byte) (n int, err error) {
 | 
						|
	if w.closed {
 | 
						|
		return 0, errClosed
 | 
						|
	}
 | 
						|
	for {
 | 
						|
		k, err := w.bw.Write(p[n:])
 | 
						|
		n += k
 | 
						|
		if err != errNoSpace {
 | 
						|
			return n, err
 | 
						|
		}
 | 
						|
		if err = w.closeBlockWriter(); err != nil {
 | 
						|
			return n, err
 | 
						|
		}
 | 
						|
		if err = w.newBlockWriter(); err != nil {
 | 
						|
			return n, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Close closes the writer and adds the footer to the Writer. Close
 | 
						|
// doesn't close the underlying writer.
 | 
						|
func (w *Writer) Close() error {
 | 
						|
	if w.closed {
 | 
						|
		return errClosed
 | 
						|
	}
 | 
						|
	w.closed = true
 | 
						|
	var err error
 | 
						|
	if err = w.closeBlockWriter(); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	f := footer{flags: w.h.flags}
 | 
						|
	if f.indexSize, err = writeIndex(w.xz, w.index); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	data, err := f.MarshalBinary()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if _, err = w.xz.Write(data); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// countingWriter is a writer that counts all data written to it.
 | 
						|
type countingWriter struct {
 | 
						|
	w io.Writer
 | 
						|
	n int64
 | 
						|
}
 | 
						|
 | 
						|
// Write writes data to the countingWriter.
 | 
						|
func (cw *countingWriter) Write(p []byte) (n int, err error) {
 | 
						|
	n, err = cw.w.Write(p)
 | 
						|
	cw.n += int64(n)
 | 
						|
	if err == nil && cw.n < 0 {
 | 
						|
		return n, errors.New("xz: counter overflow")
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// blockWriter is writes a single block.
 | 
						|
type blockWriter struct {
 | 
						|
	cxz countingWriter
 | 
						|
	// mw combines io.WriteCloser w and the hash.
 | 
						|
	mw        io.Writer
 | 
						|
	w         io.WriteCloser
 | 
						|
	n         int64
 | 
						|
	blockSize int64
 | 
						|
	closed    bool
 | 
						|
	headerLen int
 | 
						|
 | 
						|
	filters []filter
 | 
						|
	hash    hash.Hash
 | 
						|
}
 | 
						|
 | 
						|
// newBlockWriter creates a new block writer.
 | 
						|
func (c *WriterConfig) newBlockWriter(xz io.Writer, hash hash.Hash) (bw *blockWriter, err error) {
 | 
						|
	bw = &blockWriter{
 | 
						|
		cxz:       countingWriter{w: xz},
 | 
						|
		blockSize: c.BlockSize,
 | 
						|
		filters:   c.filters(),
 | 
						|
		hash:      hash,
 | 
						|
	}
 | 
						|
	bw.w, err = c.newFilterWriteCloser(&bw.cxz, bw.filters)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if bw.hash.Size() != 0 {
 | 
						|
		bw.mw = io.MultiWriter(bw.w, bw.hash)
 | 
						|
	} else {
 | 
						|
		bw.mw = bw.w
 | 
						|
	}
 | 
						|
	return bw, nil
 | 
						|
}
 | 
						|
 | 
						|
// writeHeader writes the header. If the function is called after Close
 | 
						|
// the commpressedSize and uncompressedSize fields will be filled.
 | 
						|
func (bw *blockWriter) writeHeader(w io.Writer) error {
 | 
						|
	h := blockHeader{
 | 
						|
		compressedSize:   -1,
 | 
						|
		uncompressedSize: -1,
 | 
						|
		filters:          bw.filters,
 | 
						|
	}
 | 
						|
	if bw.closed {
 | 
						|
		h.compressedSize = bw.compressedSize()
 | 
						|
		h.uncompressedSize = bw.uncompressedSize()
 | 
						|
	}
 | 
						|
	data, err := h.MarshalBinary()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if _, err = w.Write(data); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	bw.headerLen = len(data)
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// compressed size returns the amount of data written to the underlying
 | 
						|
// stream.
 | 
						|
func (bw *blockWriter) compressedSize() int64 {
 | 
						|
	return bw.cxz.n
 | 
						|
}
 | 
						|
 | 
						|
// uncompressedSize returns the number of data written to the
 | 
						|
// blockWriter
 | 
						|
func (bw *blockWriter) uncompressedSize() int64 {
 | 
						|
	return bw.n
 | 
						|
}
 | 
						|
 | 
						|
// unpaddedSize returns the sum of the header length, the uncompressed
 | 
						|
// size of the block and the hash size.
 | 
						|
func (bw *blockWriter) unpaddedSize() int64 {
 | 
						|
	if bw.headerLen <= 0 {
 | 
						|
		panic("xz: block header not written")
 | 
						|
	}
 | 
						|
	n := int64(bw.headerLen)
 | 
						|
	n += bw.compressedSize()
 | 
						|
	n += int64(bw.hash.Size())
 | 
						|
	return n
 | 
						|
}
 | 
						|
 | 
						|
// record returns the record for the current stream. Call Close before
 | 
						|
// calling this method.
 | 
						|
func (bw *blockWriter) record() record {
 | 
						|
	return record{bw.unpaddedSize(), bw.uncompressedSize()}
 | 
						|
}
 | 
						|
 | 
						|
var errClosed = errors.New("xz: writer already closed")
 | 
						|
 | 
						|
var errNoSpace = errors.New("xz: no space")
 | 
						|
 | 
						|
// Write writes uncompressed data to the block writer.
 | 
						|
func (bw *blockWriter) Write(p []byte) (n int, err error) {
 | 
						|
	if bw.closed {
 | 
						|
		return 0, errClosed
 | 
						|
	}
 | 
						|
 | 
						|
	t := bw.blockSize - bw.n
 | 
						|
	if int64(len(p)) > t {
 | 
						|
		err = errNoSpace
 | 
						|
		p = p[:t]
 | 
						|
	}
 | 
						|
 | 
						|
	var werr error
 | 
						|
	n, werr = bw.mw.Write(p)
 | 
						|
	bw.n += int64(n)
 | 
						|
	if werr != nil {
 | 
						|
		return n, werr
 | 
						|
	}
 | 
						|
	return n, err
 | 
						|
}
 | 
						|
 | 
						|
// Close closes the writer.
 | 
						|
func (bw *blockWriter) Close() error {
 | 
						|
	if bw.closed {
 | 
						|
		return errClosed
 | 
						|
	}
 | 
						|
	bw.closed = true
 | 
						|
	if err := bw.w.Close(); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	s := bw.hash.Size()
 | 
						|
	k := padLen(bw.cxz.n)
 | 
						|
	p := make([]byte, k+s)
 | 
						|
	bw.hash.Sum(p[k:k])
 | 
						|
	if _, err := bw.cxz.w.Write(p); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 |