123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399 |
- // 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
- }
|