Merge compression updates
[wimlib] / src / compress.c
1 /*
2  * compress.c
3  *
4  * Generic functions for compression, wrapping around actual compression
5  * implementations.
6  */
7
8 /*
9  * Copyright (C) 2013, 2014 Eric Biggers
10  *
11  * This file is part of wimlib, a library for working with WIM files.
12  *
13  * wimlib is free software; you can redistribute it and/or modify it under the
14  * terms of the GNU General Public License as published by the Free
15  * Software Foundation; either version 3 of the License, or (at your option)
16  * any later version.
17  *
18  * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
19  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
20  * A PARTICULAR PURPOSE. See the GNU General Public License for more
21  * details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with wimlib; if not, see http://www.gnu.org/licenses/.
25  */
26
27 #ifdef HAVE_CONFIG_H
28 #  include "config.h"
29 #endif
30
31 #include "wimlib.h"
32 #include "wimlib/assert.h"
33 #include "wimlib/error.h"
34 #include "wimlib/compressor_ops.h"
35 #include "wimlib/util.h"
36
37 #include <stdlib.h>
38 #include <string.h>
39
40 struct wimlib_compressor {
41         const struct compressor_ops *ops;
42         void *private;
43         enum wimlib_compression_type ctype;
44         size_t max_block_size;
45 };
46
47 static const struct compressor_ops *compressor_ops[] = {
48         [WIMLIB_COMPRESSION_TYPE_XPRESS] = &xpress_compressor_ops,
49         [WIMLIB_COMPRESSION_TYPE_LZX]    = &lzx_compressor_ops,
50         [WIMLIB_COMPRESSION_TYPE_LZMS]   = &lzms_compressor_ops,
51 };
52
53 /* Scale: 10 = low, 50 = medium, 100 = high */
54
55 #define DEFAULT_COMPRESSION_LEVEL 50
56
57 static unsigned int default_compression_levels[ARRAY_LEN(compressor_ops)];
58
59 static bool
60 compressor_ctype_valid(int ctype)
61 {
62         return (ctype >= 0 &&
63                 ctype < ARRAY_LEN(compressor_ops) &&
64                 compressor_ops[ctype] != NULL);
65 }
66
67 WIMLIBAPI int
68 wimlib_set_default_compression_level(enum wimlib_compression_type ctype,
69                                      unsigned int compression_level)
70 {
71         if ((int)ctype == -1) {
72                 for (int i = 0; i < ARRAY_LEN(default_compression_levels); i++)
73                         default_compression_levels[i] = compression_level;
74         } else {
75                 if (!compressor_ctype_valid(ctype))
76                         return WIMLIB_ERR_INVALID_COMPRESSION_TYPE;
77
78                 default_compression_levels[ctype] = compression_level;
79         }
80         return 0;
81 }
82
83 WIMLIBAPI u64
84 wimlib_get_compressor_needed_memory(enum wimlib_compression_type ctype,
85                                     size_t max_block_size,
86                                     unsigned int compression_level)
87 {
88         const struct compressor_ops *ops;
89         u64 size;
90
91         if (!compressor_ctype_valid(ctype))
92                 return 0;
93
94         ops = compressor_ops[ctype];
95
96         if (compression_level == 0)
97                 compression_level = default_compression_levels[ctype];
98         if (compression_level == 0)
99                 compression_level = DEFAULT_COMPRESSION_LEVEL;
100
101         size = sizeof(struct wimlib_compressor);
102         if (ops->get_needed_memory)
103                 size += ops->get_needed_memory(max_block_size, compression_level);
104         return size;
105 }
106
107 WIMLIBAPI int
108 wimlib_create_compressor(enum wimlib_compression_type ctype,
109                          size_t max_block_size,
110                          unsigned int compression_level,
111                          struct wimlib_compressor **c_ret)
112 {
113         struct wimlib_compressor *c;
114
115         if (c_ret == NULL)
116                 return WIMLIB_ERR_INVALID_PARAM;
117
118         if (max_block_size == 0)
119                 return WIMLIB_ERR_INVALID_PARAM;
120
121         if (!compressor_ctype_valid(ctype))
122                 return WIMLIB_ERR_INVALID_COMPRESSION_TYPE;
123
124         c = MALLOC(sizeof(*c));
125         if (c == NULL)
126                 return WIMLIB_ERR_NOMEM;
127         c->ops = compressor_ops[ctype];
128         c->private = NULL;
129         c->ctype = ctype;
130         c->max_block_size = max_block_size;
131         if (c->ops->create_compressor) {
132                 int ret;
133
134                 if (compression_level == 0)
135                         compression_level = default_compression_levels[ctype];
136                 if (compression_level == 0)
137                         compression_level = DEFAULT_COMPRESSION_LEVEL;
138
139                 ret = c->ops->create_compressor(max_block_size,
140                                                 compression_level,
141                                                 &c->private);
142                 if (ret) {
143                         FREE(c);
144                         return ret;
145                 }
146         }
147         *c_ret = c;
148         return 0;
149 }
150
151 WIMLIBAPI size_t
152 wimlib_compress(const void *uncompressed_data, size_t uncompressed_size,
153                 void *compressed_data, size_t compressed_size_avail,
154                 struct wimlib_compressor *c)
155 {
156         size_t compressed_size;
157
158         wimlib_assert(uncompressed_size <= c->max_block_size);
159
160         compressed_size = c->ops->compress(uncompressed_data,
161                                            uncompressed_size,
162                                            compressed_data,
163                                            compressed_size_avail,
164                                            c->private);
165
166         /* (Optional) Verify that we really get the same thing back when
167          * decompressing.  Should always be the case, unless there's a bug.  */
168 #ifdef ENABLE_VERIFY_COMPRESSION
169         if (compressed_size != 0) {
170                 struct wimlib_decompressor *d;
171                 int res;
172                 u8 *buf;
173
174                 buf = MALLOC(uncompressed_size);
175                 if (!buf) {
176                         WARNING("Unable to verify results of %s compression "
177                                 "(can't allocate buffer)",
178                                 wimlib_get_compression_type_string(c->ctype));
179                         return 0;
180                 }
181
182                 res = wimlib_create_decompressor(c->ctype,
183                                                  c->max_block_size, &d);
184                 if (res) {
185                         WARNING("Unable to verify results of %s compression "
186                                 "(can't create decompressor)",
187                                 wimlib_get_compression_type_string(c->ctype));
188                         FREE(buf);
189                         return 0;
190                 }
191
192                 res = wimlib_decompress(compressed_data, compressed_size,
193                                         buf, uncompressed_size, d);
194                 wimlib_free_decompressor(d);
195                 if (res) {
196                         ERROR("Failed to decompress our %s-compressed data",
197                               wimlib_get_compression_type_string(c->ctype));
198                         FREE(buf);
199                         abort();
200                 }
201
202                 res = memcmp(uncompressed_data, buf, uncompressed_size);
203                 FREE(buf);
204
205                 if (res) {
206                         ERROR("Our %s-compressed data did not decompress "
207                               "to original",
208                               wimlib_get_compression_type_string(c->ctype));
209                         abort();
210                 }
211         }
212 #endif /* ENABLE_VERIFY_COMPRESSION */
213
214         return compressed_size;
215 }
216
217 WIMLIBAPI void
218 wimlib_free_compressor(struct wimlib_compressor *c)
219 {
220         if (c) {
221                 if (c->ops->free_compressor)
222                         c->ops->free_compressor(c->private);
223                 FREE(c);
224         }
225 }