1 | /* |
2 | Copyright (c) 2015-2016, Apple Inc. All rights reserved. |
3 | |
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: |
5 | |
6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. |
7 | |
8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer |
9 | in the documentation and/or other materials provided with the distribution. |
10 | |
11 | 3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived |
12 | from this software without specific prior written permission. |
13 | |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
15 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
16 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
18 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
19 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
20 | */ |
21 | |
22 | // LZFSE command line tool |
23 | |
24 | #include "lzfse.h" |
25 | #include <fcntl.h> |
26 | #include <stdio.h> |
27 | #include <stdlib.h> |
28 | #include <string.h> |
29 | #include <sys/stat.h> |
30 | #include <unistd.h> |
31 | |
32 | // Same as realloc(x,s), except x is freed when realloc fails |
33 | static inline void *lzfse_reallocf(void *x, size_t s) { |
34 | void *y = realloc(x, s); |
35 | if (y == 0) { |
36 | free(x); |
37 | return 0; |
38 | } |
39 | return y; |
40 | } |
41 | |
42 | // Get wall clock time |
43 | #include <sys/time.h> |
44 | |
45 | static double get_time() { |
46 | struct timeval tv; |
47 | if (gettimeofday(&tv, 0) != 0) { |
48 | perror("gettimeofday" ); |
49 | exit(1); |
50 | } |
51 | return (double)tv.tv_sec + 1.0e-6 * (double)tv.tv_usec; |
52 | } |
53 | |
54 | //-------------------- |
55 | |
56 | enum { LZFSE_ENCODE = 0, LZFSE_DECODE }; |
57 | |
58 | void usage(int argc, char **argv) { |
59 | fprintf( |
60 | stderr, |
61 | "Usage: %s -encode|-decode [-i input_file] [-o output_file] [-h] [-v]\n" , |
62 | argv[0]); |
63 | } |
64 | |
65 | #define USAGE(argc, argv) \ |
66 | do { \ |
67 | usage(argc, argv); \ |
68 | exit(0); \ |
69 | } while (0) |
70 | #define USAGE_MSG(argc, argv, ...) \ |
71 | do { \ |
72 | usage(argc, argv); \ |
73 | fprintf(stderr, __VA_ARGS__); \ |
74 | exit(1); \ |
75 | } while (0) |
76 | |
77 | int main(int argc, char **argv) { |
78 | const char *in_file = 0; // stdin |
79 | const char *out_file = 0; // stdout |
80 | int op = -1; // invalid op |
81 | int verbosity = 0; // quiet |
82 | |
83 | // Parse options |
84 | for (int i = 1; i < argc;) { |
85 | // no args |
86 | const char *a = argv[i++]; |
87 | if (strcmp(a, "-h" ) == 0) |
88 | USAGE(argc, argv); |
89 | if (strcmp(a, "-v" ) == 0) { |
90 | verbosity++; |
91 | continue; |
92 | } |
93 | if (strcmp(a, "-encode" ) == 0) { |
94 | op = LZFSE_ENCODE; |
95 | continue; |
96 | } |
97 | if (strcmp(a, "-decode" ) == 0) { |
98 | op = LZFSE_DECODE; |
99 | continue; |
100 | } |
101 | |
102 | // one arg |
103 | const char **arg_var = 0; |
104 | if (strcmp(a, "-i" ) == 0 && in_file == 0) |
105 | arg_var = &in_file; |
106 | else if (strcmp(a, "-o" ) == 0 && out_file == 0) |
107 | arg_var = &out_file; |
108 | if (arg_var != 0) { |
109 | // Flag is recognized. Check if there is an argument. |
110 | if (i == argc) |
111 | USAGE_MSG(argc, argv, "Error: Missing arg after %s\n" , a); |
112 | *arg_var = argv[i++]; |
113 | continue; |
114 | } |
115 | |
116 | USAGE_MSG(argc, argv, "Error: invalid flag %s\n" , a); |
117 | } |
118 | if (op < 0) |
119 | USAGE_MSG(argc, argv, "Error: -encode|-decode required\n" ); |
120 | |
121 | // Info |
122 | if (verbosity > 0) { |
123 | if (op == LZFSE_ENCODE) |
124 | fprintf(stderr, "LZFSE encode\n" ); |
125 | if (op == LZFSE_DECODE) |
126 | fprintf(stderr, "LZFSE decode\n" ); |
127 | fprintf(stderr, "Input: %s\n" , in_file ? in_file : "stdin" ); |
128 | fprintf(stderr, "Output: %s\n" , out_file ? out_file : "stdout" ); |
129 | } |
130 | |
131 | // Load input |
132 | size_t in_allocated = 0; // allocated in IN |
133 | size_t in_size = 0; // used in IN |
134 | uint8_t *in = 0; // input buffer |
135 | int in_fd = -1; // input file desc |
136 | |
137 | if (in_file != 0) { |
138 | // If we have a file name, open it, and allocate the exact input size |
139 | struct stat st; |
140 | in_fd = open(in_file, O_RDONLY); |
141 | if (in_fd < 0) { |
142 | perror(in_file); |
143 | exit(1); |
144 | } |
145 | if (fstat(in_fd, &st) != 0) { |
146 | perror(in_file); |
147 | exit(1); |
148 | } |
149 | if (st.st_size > SIZE_MAX) { |
150 | fprintf(stderr, "File is too large\n" ); |
151 | exit(1); |
152 | } |
153 | in_allocated = (size_t)st.st_size; |
154 | } else { |
155 | // Otherwise, read from stdin, and allocate to 1 MB, grow as needed |
156 | in_allocated = 1 << 20; |
157 | in_fd = 0; |
158 | } |
159 | in = (uint8_t *)malloc(in_allocated); |
160 | if (in == 0) { |
161 | perror("malloc" ); |
162 | exit(1); |
163 | } |
164 | |
165 | while (1) { |
166 | // re-alloc if needed |
167 | if (in_size == in_allocated) { |
168 | if (in_allocated < (100 << 20)) |
169 | in_allocated <<= 1; // double it |
170 | else |
171 | in_allocated += (100 << 20); // or add 100 MB if already large |
172 | in = lzfse_reallocf(in, in_allocated); |
173 | if (in == 0) { |
174 | perror("malloc" ); |
175 | exit(1); |
176 | } |
177 | } |
178 | |
179 | ssize_t r = read(in_fd, in + in_size, in_allocated - in_size); |
180 | if (r < 0) { |
181 | perror("read" ); |
182 | exit(1); |
183 | } |
184 | if (r == 0) |
185 | break; // end of file |
186 | in_size += (size_t)r; |
187 | } |
188 | |
189 | if (in_file != 0) { |
190 | close(in_fd); |
191 | in_fd = -1; |
192 | } |
193 | |
194 | // Size info |
195 | if (verbosity > 0) { |
196 | fprintf(stderr, "Input size: %zu B\n" , in_size); |
197 | } |
198 | |
199 | // Encode/decode |
200 | // Compute size for result buffer; we assume here that encode shrinks size, |
201 | // and that decode grows by no more than 4x. These are reasonable common- |
202 | // case guidelines, but are not formally guaranteed to be satisfied. |
203 | size_t out_allocated = (op == LZFSE_ENCODE) ? in_size : (4 * in_size); |
204 | size_t out_size = 0; |
205 | size_t aux_allocated = (op == LZFSE_ENCODE) ? lzfse_encode_scratch_size() |
206 | : lzfse_decode_scratch_size(); |
207 | void *aux = aux_allocated ? malloc(aux_allocated) : 0; |
208 | if (aux_allocated != 0 && aux == 0) { |
209 | perror("malloc" ); |
210 | exit(1); |
211 | } |
212 | uint8_t *out = (uint8_t *)malloc(out_allocated); |
213 | if (out == 0) { |
214 | perror("malloc" ); |
215 | exit(1); |
216 | } |
217 | |
218 | double c0 = get_time(); |
219 | while (1) { |
220 | if (op == LZFSE_ENCODE) |
221 | out_size = lzfse_encode_buffer(out, out_allocated, in, in_size, aux); |
222 | else |
223 | out_size = lzfse_decode_buffer(out, out_allocated, in, in_size, aux); |
224 | |
225 | // If output buffer was too small, grow and retry. |
226 | if (out_size == 0 || (op == LZFSE_DECODE && out_size == out_allocated)) { |
227 | if (verbosity > 0) |
228 | fprintf(stderr, "Output buffer was too small, increasing size...\n" ); |
229 | out_allocated <<= 1; |
230 | out = (uint8_t *)lzfse_reallocf(out, out_allocated); |
231 | if (out == 0) { |
232 | perror("malloc" ); |
233 | exit(1); |
234 | } |
235 | continue; |
236 | } |
237 | |
238 | break; |
239 | } |
240 | double c1 = get_time(); |
241 | |
242 | if (verbosity > 0) { |
243 | fprintf(stderr, "Output size: %zu B\n" , out_size); |
244 | size_t raw_size = (op == LZFSE_ENCODE) ? in_size : out_size; |
245 | size_t compressed_size = (op == LZFSE_ENCODE) ? out_size : in_size; |
246 | fprintf(stderr, "Compression ratio: %.3f\n" , |
247 | (double)raw_size / (double)compressed_size); |
248 | double ns_per_byte = 1.0e9 * (c1 - c0) / (double)raw_size; |
249 | double mb_per_s = (double)raw_size / 1024.0 / 1024.0 / (c1 - c0); |
250 | fprintf(stderr, "Speed: %.2f ns/B, %.2f MB/s\n" ,ns_per_byte,mb_per_s); |
251 | } |
252 | |
253 | // Write output |
254 | int out_fd = -1; |
255 | if (out_file) { |
256 | out_fd = open(out_file, O_WRONLY | O_CREAT | O_TRUNC, 0644); |
257 | if (out_fd < 0) { |
258 | perror(out_file); |
259 | exit(1); |
260 | } |
261 | } else { |
262 | out_fd = 1; // stdout |
263 | } |
264 | for (size_t out_pos = 0; out_pos < out_size;) { |
265 | ssize_t w = write(out_fd, out + out_pos, out_size - out_pos); |
266 | if (w < 0) { |
267 | perror("write" ); |
268 | exit(1); |
269 | } |
270 | if (w == 0) { |
271 | fprintf(stderr, "Failed to write to output file\n" ); |
272 | exit(1); |
273 | } |
274 | out_pos += (size_t)w; |
275 | } |
276 | if (out_file != 0) { |
277 | close(out_fd); |
278 | out_fd = -1; |
279 | } |
280 | |
281 | free(in); |
282 | free(out); |
283 | free(aux); |
284 | return 0; // OK |
285 | } |
286 | |