1 // Copyright 2016 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <errno.h>
6 #include <fcntl.h>
7 #include <stdbool.h>
8 #include <stdint.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <sys/stat.h>
13 #include <sys/types.h>
14 #include <unistd.h>
15 
16 #include <lz4/lz4frame.h>
17 
18 #define BLOCK_SIZE 65536
19 
20 #define WR_NEWFILE O_WRONLY | O_CREAT | O_TRUNC
21 #define PERM_644 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
22 
usage(const char * arg0)23 static void usage(const char* arg0) {
24     printf("usage: %s [-1|-9] [-d] <input file> <output file>\n", arg0);
25     printf("   -1  fast compression (default)\n");
26     printf("   -9  high compression (slower)\n");
27     printf("   -d  decompress\n");
28 }
29 
do_decompress(const char * infile,const char * outfile)30 static int do_decompress(const char* infile, const char* outfile) {
31     int infd, outfd;
32 
33     infd = open(infile, O_RDONLY);
34     if (infd < 0) {
35         fprintf(stderr, "could not open %s: %s\n", infile, strerror(errno));
36         return -1;
37     }
38 
39     outfd = open(outfile, WR_NEWFILE, PERM_644);
40     if (outfd < 0) {
41         fprintf(stderr, "could not open %s: %s\n", outfile, strerror(errno));
42         close(infd);
43         return -1;
44     }
45 
46     LZ4F_decompressionContext_t dctx;
47     LZ4F_errorCode_t errc = LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION);
48     if (LZ4F_isError(errc)) {
49         fprintf(stderr, "could not initialize decompression: %s\n", LZ4F_getErrorName(errc));
50         close(outfd);
51         close(infd);
52         return -1;
53     }
54 
55     uint8_t inbuf[BLOCK_SIZE];
56     uint8_t outbuf[BLOCK_SIZE];
57 
58     // Read first 4 bytes to let LZ4 tell us how much it expects in the first
59     // pass.
60     size_t src_sz = 4;
61     size_t dst_sz = 0;
62     ssize_t nr = read(infd, inbuf, src_sz);
63     if (nr < (ssize_t)src_sz) {
64         fprintf(stderr, "could not read from %s", infile);
65         if (nr < 0) {
66             fprintf(stderr, ": %s", strerror(errno));
67         }
68         fprintf(stderr, "\n");
69         goto done;
70     }
71     size_t to_read = LZ4F_decompress(dctx, outbuf, &dst_sz, inbuf, &src_sz, NULL);
72     if (LZ4F_isError(to_read)) {
73         fprintf(stderr, "could not decompress %s: %s\n", infile, LZ4F_getErrorName(to_read));
74         goto done;
75     }
76     if (to_read > BLOCK_SIZE) {
77         to_read = BLOCK_SIZE;
78     }
79 
80     while ((nr = read(infd, inbuf, to_read)) > 0) {
81         src_sz = nr;
82         ssize_t pos = 0;
83         size_t next = 0;
84 
85         while (pos < nr) {
86             dst_sz = BLOCK_SIZE;
87             next = LZ4F_decompress(dctx, outbuf, &dst_sz, inbuf + pos, &src_sz, NULL);
88             if (LZ4F_isError(next)) {
89                 fprintf(stderr, "could not decompress %s: %s\n", infile, LZ4F_getErrorName(to_read));
90                 goto done;
91             }
92 
93             if (dst_sz) {
94                 ssize_t nw = write(outfd, outbuf, dst_sz);
95                 if (nw != (ssize_t)dst_sz) {
96                     fprintf(stderr, "could not write to %s", outfile);
97                     if (nw < 0) {
98                         fprintf(stderr, ": %s", strerror(errno));
99                     }
100                     fprintf(stderr, "\n");
101                     goto done;
102                 }
103             }
104             pos += src_sz;
105             src_sz = nr - pos;
106         }
107 
108         to_read = next;
109         if (to_read > BLOCK_SIZE || to_read == 0) {
110             to_read = BLOCK_SIZE;
111         }
112     }
113 
114     if (nr < 0) {
115         fprintf(stderr, "error reading %s: %s\n", infile, strerror(errno));
116         goto done;
117     }
118 
119 done:
120     LZ4F_freeDecompressionContext(dctx);
121     close(outfd);
122     close(infd);
123     return 0;
124 }
125 
do_compress(const char * infile,const char * outfile,int clevel)126 static int do_compress(const char* infile, const char* outfile, int clevel) {
127     int infd, outfd;
128 
129     infd = open(infile, O_RDONLY);
130     if (infd < 0) {
131         fprintf(stderr, "could not open %s: %s\n", infile, strerror(errno));
132         return -1;
133     }
134 
135     outfd = open(outfile, WR_NEWFILE, PERM_644);
136     if (outfd < 0) {
137         fprintf(stderr, "could not open %s: %s\n", outfile, strerror(errno));
138         close(infd);
139         return -1;
140     }
141 
142     LZ4F_preferences_t prefs;
143     memset(&prefs, 0, sizeof(prefs));
144     prefs.compressionLevel = clevel;
145 
146     uint8_t inbuf[BLOCK_SIZE];
147     size_t outsize = LZ4F_compressFrameBound(BLOCK_SIZE, &prefs);
148     uint8_t* outbuf = malloc(outsize);
149     if (!outbuf) {
150         fprintf(stderr, "out of memory\n");
151         close(outfd);
152         close(infd);
153         return ENOMEM;
154     }
155 
156     // TODO: do the whole file in one frame, using the LZ4F begin/update/end
157     // functions.
158     ssize_t nr = 0;
159     while ((nr = read(infd, inbuf, BLOCK_SIZE)) > 0) {
160         ssize_t csz = LZ4F_compressFrame(outbuf, outsize, inbuf, nr, &prefs);
161         if (LZ4F_isError(csz)) {
162             fprintf(stderr, "error compressing %s: %s\n", infile, LZ4F_getErrorName(csz));
163             goto done;
164         }
165 
166         ssize_t nw = write(outfd, outbuf, csz);
167         if (nw != csz) {
168             fprintf(stderr, "could not write to %s", outfile);
169             if (nw < 0) {
170                 fprintf(stderr, ": %s", strerror(errno));
171             }
172             fprintf(stderr, "\n");
173             goto done;
174         }
175     }
176 
177     if (nr < 0) {
178         fprintf(stderr, "error reading %s: %s\n", infile, strerror(errno));
179         goto done;
180     }
181 
182 done:
183     free(outbuf);
184     close(outfd);
185     close(infd);
186     return 0;
187 }
188 
main(int argc,char * argv[])189 int main(int argc, char* argv[]) {
190     int clevel = 1;
191     bool decompress = false;
192     const char* infile = NULL;
193     const char* outfile = NULL;
194 
195     for (int i = 1; i < argc; i++) {
196         if (!strcmp("-d", argv[i])) {
197             decompress = true;
198             continue;
199         }
200         if (!strcmp("-9", argv[i])) {
201             clevel = 9;
202             continue;
203         }
204         if (!strcmp("-h", argv[i])) {
205             usage(argv[0]);
206             return 0;
207         }
208 
209         if (!infile) {
210             infile = argv[i];
211             continue;
212         }
213         if (!outfile) {
214             outfile = argv[i];
215             continue;
216         }
217 
218         fprintf(stderr, "Unknown argument: %s\n", argv[i]);
219         usage(argv[0]);
220         return -1;
221     }
222 
223     if (!infile || !outfile) {
224         usage(argv[0]);
225         return 0;
226     }
227 
228     printf("%scompressing %s into %s",
229             decompress ? "de" : "",
230             infile,
231             outfile);
232     if (!decompress) {
233         printf(" at level %d", clevel);
234     }
235     printf("\n");
236 
237     if (decompress) {
238         return do_decompress(infile, outfile);
239     } else {
240         return do_compress(infile, outfile, clevel);
241     }
242 }
243