Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
399 lines
8.7 KiB
399 lines
8.7 KiB
// 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 |
|
}
|
|
|