1/*
2Copyright (c) 2015-2016, Apple Inc. All rights reserved.
3
4Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
61. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
82. 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
113. 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
14THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
15LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
16COPYRIGHT 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)
18HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
19ARISING 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
33static 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
45static 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
56enum { LZFSE_ENCODE = 0, LZFSE_DECODE };
57
58void 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
77int 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