main
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}