main
Raw Download raw file
  1// Copyright 2014-2022 Ulrich Kunitz. 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
  5package xz
  6
  7import (
  8	"errors"
  9	"fmt"
 10	"hash"
 11	"io"
 12
 13	"github.com/ulikunitz/xz/lzma"
 14)
 15
 16// WriterConfig describe the parameters for an xz writer.
 17type WriterConfig struct {
 18	Properties *lzma.Properties
 19	DictCap    int
 20	BufSize    int
 21	BlockSize  int64
 22	// checksum method: CRC32, CRC64 or SHA256 (default: CRC64)
 23	CheckSum byte
 24	// Forces NoChecksum (default: false)
 25	NoCheckSum bool
 26	// match algorithm
 27	Matcher lzma.MatchAlgorithm
 28}
 29
 30// fill replaces zero values with default values.
 31func (c *WriterConfig) fill() {
 32	if c.Properties == nil {
 33		c.Properties = &lzma.Properties{LC: 3, LP: 0, PB: 2}
 34	}
 35	if c.DictCap == 0 {
 36		c.DictCap = 8 * 1024 * 1024
 37	}
 38	if c.BufSize == 0 {
 39		c.BufSize = 4096
 40	}
 41	if c.BlockSize == 0 {
 42		c.BlockSize = maxInt64
 43	}
 44	if c.CheckSum == 0 {
 45		c.CheckSum = CRC64
 46	}
 47	if c.NoCheckSum {
 48		c.CheckSum = None
 49	}
 50}
 51
 52// Verify checks the configuration for errors. Zero values will be
 53// replaced by default values.
 54func (c *WriterConfig) Verify() error {
 55	if c == nil {
 56		return errors.New("xz: writer configuration is nil")
 57	}
 58	c.fill()
 59	lc := lzma.Writer2Config{
 60		Properties: c.Properties,
 61		DictCap:    c.DictCap,
 62		BufSize:    c.BufSize,
 63		Matcher:    c.Matcher,
 64	}
 65	if err := lc.Verify(); err != nil {
 66		return err
 67	}
 68	if c.BlockSize <= 0 {
 69		return errors.New("xz: block size out of range")
 70	}
 71	if err := verifyFlags(c.CheckSum); err != nil {
 72		return err
 73	}
 74	return nil
 75}
 76
 77// filters creates the filter list for the given parameters.
 78func (c *WriterConfig) filters() []filter {
 79	return []filter{&lzmaFilter{int64(c.DictCap)}}
 80}
 81
 82// maxInt64 defines the maximum 64-bit signed integer.
 83const maxInt64 = 1<<63 - 1
 84
 85// verifyFilters checks the filter list for the length and the right
 86// sequence of filters.
 87func verifyFilters(f []filter) error {
 88	if len(f) == 0 {
 89		return errors.New("xz: no filters")
 90	}
 91	if len(f) > 4 {
 92		return errors.New("xz: more than four filters")
 93	}
 94	for _, g := range f[:len(f)-1] {
 95		if g.last() {
 96			return errors.New("xz: last filter is not last")
 97		}
 98	}
 99	if !f[len(f)-1].last() {
100		return errors.New("xz: wrong last filter")
101	}
102	return nil
103}
104
105// newFilterWriteCloser converts a filter list into a WriteCloser that
106// can be used by a blockWriter.
107func (c *WriterConfig) newFilterWriteCloser(w io.Writer, f []filter) (fw io.WriteCloser, err error) {
108	if err = verifyFilters(f); err != nil {
109		return nil, err
110	}
111	fw = nopWriteCloser(w)
112	for i := len(f) - 1; i >= 0; i-- {
113		fw, err = f[i].writeCloser(fw, c)
114		if err != nil {
115			return nil, err
116		}
117	}
118	return fw, nil
119}
120
121// nopWCloser implements a WriteCloser with a Close method not doing
122// anything.
123type nopWCloser struct {
124	io.Writer
125}
126
127// Close returns nil and doesn't do anything else.
128func (c nopWCloser) Close() error {
129	return nil
130}
131
132// nopWriteCloser converts the Writer into a WriteCloser with a Close
133// function that does nothing beside returning nil.
134func nopWriteCloser(w io.Writer) io.WriteCloser {
135	return nopWCloser{w}
136}
137
138// Writer compresses data written to it. It is an io.WriteCloser.
139type Writer struct {
140	WriterConfig
141
142	xz      io.Writer
143	bw      *blockWriter
144	newHash func() hash.Hash
145	h       header
146	index   []record
147	closed  bool
148}
149
150// newBlockWriter creates a new block writer writes the header out.
151func (w *Writer) newBlockWriter() error {
152	var err error
153	w.bw, err = w.WriterConfig.newBlockWriter(w.xz, w.newHash())
154	if err != nil {
155		return err
156	}
157	if err = w.bw.writeHeader(w.xz); err != nil {
158		return err
159	}
160	return nil
161}
162
163// closeBlockWriter closes a block writer and records the sizes in the
164// index.
165func (w *Writer) closeBlockWriter() error {
166	var err error
167	if err = w.bw.Close(); err != nil {
168		return err
169	}
170	w.index = append(w.index, w.bw.record())
171	return nil
172}
173
174// NewWriter creates a new xz writer using default parameters.
175func NewWriter(xz io.Writer) (w *Writer, err error) {
176	return WriterConfig{}.NewWriter(xz)
177}
178
179// NewWriter creates a new Writer using the given configuration parameters.
180func (c WriterConfig) NewWriter(xz io.Writer) (w *Writer, err error) {
181	if err = c.Verify(); err != nil {
182		return nil, err
183	}
184	w = &Writer{
185		WriterConfig: c,
186		xz:           xz,
187		h:            header{c.CheckSum},
188		index:        make([]record, 0, 4),
189	}
190	if w.newHash, err = newHashFunc(c.CheckSum); err != nil {
191		return nil, err
192	}
193	data, err := w.h.MarshalBinary()
194	if err != nil {
195		return nil, fmt.Errorf("w.h.MarshalBinary(): error %w", err)
196	}
197	if _, err = xz.Write(data); err != nil {
198		return nil, err
199	}
200	if err = w.newBlockWriter(); err != nil {
201		return nil, err
202	}
203	return w, nil
204
205}
206
207// Write compresses the uncompressed data provided.
208func (w *Writer) Write(p []byte) (n int, err error) {
209	if w.closed {
210		return 0, errClosed
211	}
212	for {
213		k, err := w.bw.Write(p[n:])
214		n += k
215		if err != errNoSpace {
216			return n, err
217		}
218		if err = w.closeBlockWriter(); err != nil {
219			return n, err
220		}
221		if err = w.newBlockWriter(); err != nil {
222			return n, err
223		}
224	}
225}
226
227// Close closes the writer and adds the footer to the Writer. Close
228// doesn't close the underlying writer.
229func (w *Writer) Close() error {
230	if w.closed {
231		return errClosed
232	}
233	w.closed = true
234	var err error
235	if err = w.closeBlockWriter(); err != nil {
236		return err
237	}
238
239	f := footer{flags: w.h.flags}
240	if f.indexSize, err = writeIndex(w.xz, w.index); err != nil {
241		return err
242	}
243	data, err := f.MarshalBinary()
244	if err != nil {
245		return err
246	}
247	if _, err = w.xz.Write(data); err != nil {
248		return err
249	}
250	return nil
251}
252
253// countingWriter is a writer that counts all data written to it.
254type countingWriter struct {
255	w io.Writer
256	n int64
257}
258
259// Write writes data to the countingWriter.
260func (cw *countingWriter) Write(p []byte) (n int, err error) {
261	n, err = cw.w.Write(p)
262	cw.n += int64(n)
263	if err == nil && cw.n < 0 {
264		return n, errors.New("xz: counter overflow")
265	}
266	return
267}
268
269// blockWriter is writes a single block.
270type blockWriter struct {
271	cxz countingWriter
272	// mw combines io.WriteCloser w and the hash.
273	mw        io.Writer
274	w         io.WriteCloser
275	n         int64
276	blockSize int64
277	closed    bool
278	headerLen int
279
280	filters []filter
281	hash    hash.Hash
282}
283
284// newBlockWriter creates a new block writer.
285func (c *WriterConfig) newBlockWriter(xz io.Writer, hash hash.Hash) (bw *blockWriter, err error) {
286	bw = &blockWriter{
287		cxz:       countingWriter{w: xz},
288		blockSize: c.BlockSize,
289		filters:   c.filters(),
290		hash:      hash,
291	}
292	bw.w, err = c.newFilterWriteCloser(&bw.cxz, bw.filters)
293	if err != nil {
294		return nil, err
295	}
296	if bw.hash.Size() != 0 {
297		bw.mw = io.MultiWriter(bw.w, bw.hash)
298	} else {
299		bw.mw = bw.w
300	}
301	return bw, nil
302}
303
304// writeHeader writes the header. If the function is called after Close
305// the commpressedSize and uncompressedSize fields will be filled.
306func (bw *blockWriter) writeHeader(w io.Writer) error {
307	h := blockHeader{
308		compressedSize:   -1,
309		uncompressedSize: -1,
310		filters:          bw.filters,
311	}
312	if bw.closed {
313		h.compressedSize = bw.compressedSize()
314		h.uncompressedSize = bw.uncompressedSize()
315	}
316	data, err := h.MarshalBinary()
317	if err != nil {
318		return err
319	}
320	if _, err = w.Write(data); err != nil {
321		return err
322	}
323	bw.headerLen = len(data)
324	return nil
325}
326
327// compressed size returns the amount of data written to the underlying
328// stream.
329func (bw *blockWriter) compressedSize() int64 {
330	return bw.cxz.n
331}
332
333// uncompressedSize returns the number of data written to the
334// blockWriter
335func (bw *blockWriter) uncompressedSize() int64 {
336	return bw.n
337}
338
339// unpaddedSize returns the sum of the header length, the uncompressed
340// size of the block and the hash size.
341func (bw *blockWriter) unpaddedSize() int64 {
342	if bw.headerLen <= 0 {
343		panic("xz: block header not written")
344	}
345	n := int64(bw.headerLen)
346	n += bw.compressedSize()
347	n += int64(bw.hash.Size())
348	return n
349}
350
351// record returns the record for the current stream. Call Close before
352// calling this method.
353func (bw *blockWriter) record() record {
354	return record{bw.unpaddedSize(), bw.uncompressedSize()}
355}
356
357var errClosed = errors.New("xz: writer already closed")
358
359var errNoSpace = errors.New("xz: no space")
360
361// Write writes uncompressed data to the block writer.
362func (bw *blockWriter) Write(p []byte) (n int, err error) {
363	if bw.closed {
364		return 0, errClosed
365	}
366
367	t := bw.blockSize - bw.n
368	if int64(len(p)) > t {
369		err = errNoSpace
370		p = p[:t]
371	}
372
373	var werr error
374	n, werr = bw.mw.Write(p)
375	bw.n += int64(n)
376	if werr != nil {
377		return n, werr
378	}
379	return n, err
380}
381
382// Close closes the writer.
383func (bw *blockWriter) Close() error {
384	if bw.closed {
385		return errClosed
386	}
387	bw.closed = true
388	if err := bw.w.Close(); err != nil {
389		return err
390	}
391	s := bw.hash.Size()
392	k := padLen(bw.cxz.n)
393	p := make([]byte, k+s)
394	bw.hash.Sum(p[k:k])
395	if _, err := bw.cxz.w.Write(p); err != nil {
396		return err
397	}
398	return nil
399}