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