1 /* -*- indent-tabs-mode: nil -*-
2  *
3  * Copyright 2011-2015 Kubo Takehiro <kubo@jiubao.org>
4  *
5  * Redistribution and use in source and binary forms, with or without modification, are
6  * permitted provided that the following conditions are met:
7  *
8  *    1. Redistributions of source code must retain the above copyright notice, this list of
9  *       conditions and the following disclaimer.
10  *
11  *    2. Redistributions in binary form must reproduce the above copyright notice, this list
12  *       of conditions and the following disclaimer in the documentation and/or other materials
13  *       provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS OR IMPLIED
16  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
17  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
21  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
22  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
23  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  *
25  * The views and conclusions contained in the software and documentation are those of the
26  * authors and should not be interpreted as representing official policies, either expressed
27  * or implied, of the authors.
28  *
29  */
30 #ifdef HAVE_CONFIG_H
31 #include "config.h"
32 #endif
33 
34 #include <stdlib.h>
35 #include <string.h>
36 #include <errno.h>
37 #include <snappy-c.h>
38 #ifdef WIN32
39 #include <winsock2.h>
40 #else
41 #include <arpa/inet.h>
42 #endif
43 #include "snzip.h"
44 
45 #define SNAPPY_JAVA_MAGIC "\x82SNAPPY\x00"
46 #define SNAPPY_JAVA_MAGIC_LEN 8
47 #define SNAPPY_JAVA_FILE_VERSION 1
48 #define DEFAULT_BLOCK_SIZE (32 * 1024) // Use 32kb for the default block size
49 
50 typedef struct {
51   char magic[SNAPPY_JAVA_MAGIC_LEN];
52   int version;
53   int compatible_version;
54 } snappy_java_header_t;
55 
snappy_java_compress(FILE * infp,FILE * outfp,size_t block_size)56 static int snappy_java_compress(FILE *infp, FILE *outfp, size_t block_size)
57 {
58   snappy_java_header_t header;
59   work_buffer_t wb;
60   size_t uncompressed_length;
61   int err = 1;
62 
63   wb.c = NULL;
64   wb.uc = NULL;
65 
66   if (block_size == 0) {
67     block_size = DEFAULT_BLOCK_SIZE;
68   }
69 
70   /* write the file header */
71   memcpy(header.magic, SNAPPY_JAVA_MAGIC, SNAPPY_JAVA_MAGIC_LEN);
72   header.version = SNZ_TO_BE32(SNAPPY_JAVA_FILE_VERSION);
73   header.compatible_version = SNZ_TO_BE32(SNAPPY_JAVA_FILE_VERSION);
74 
75   if (fwrite(&header, sizeof(header), 1, outfp) != 1) {
76     print_error("Failed to write a file: %s\n", strerror(errno));
77     goto cleanup;
78   }
79 
80   /* write file body */
81   work_buffer_init(&wb, block_size);
82   while ((uncompressed_length = fread(wb.uc, 1, wb.uclen, infp)) > 0) {
83     size_t compressed_length = wb.clen;
84 
85     trace("read %lu bytes.\n", (unsigned long)uncompressed_length);
86 
87     /* compress the block. */
88     snappy_compress(wb.uc, uncompressed_length, wb.c, &compressed_length);
89     trace("compressed_legnth is %lu.\n", (unsigned long)compressed_length);
90 
91     /* write the compressed length. */
92     putc((compressed_length >> 24), outfp);
93     putc((compressed_length >> 16), outfp);
94     putc((compressed_length >>  8), outfp);
95     putc((compressed_length >>  0), outfp);
96     trace("write 4 bytes for compressed data length.\n");
97 
98     /* write the compressed data. */
99     if (fwrite(wb.c, compressed_length, 1, outfp) != 1) {
100       print_error("Failed to write a file: %s\n", strerror(errno));
101       goto cleanup;
102     }
103     trace("write %ld bytes for compressed data.\n", (long)compressed_length);
104   }
105   if (!feof(infp)) {
106     /* fread() failed. */
107     print_error("Failed to read a file: %s\n", strerror(errno));
108     goto cleanup;
109   }
110   err = 0;
111  cleanup:
112   work_buffer_free(&wb);
113   return err;
114 }
115 
snappy_java_uncompress(FILE * infp,FILE * outfp,int skip_magic)116 static int snappy_java_uncompress(FILE *infp, FILE *outfp, int skip_magic)
117 {
118   snappy_java_header_t header;
119   work_buffer_t wb;
120   int err = 1;
121   int outfd;
122 
123   wb.c = NULL;
124   wb.uc = NULL;
125 
126   if (skip_magic) {
127     /* read header except magic */
128     if (fread(&header.version, sizeof(header) - sizeof(header.magic), 1, infp) != 1) {
129       print_error("Failed to read a file: %s\n", strerror(errno));
130       goto cleanup;
131     }
132   } else {
133     /* read header */
134     if (fread(&header, sizeof(header), 1, infp) != 1) {
135       print_error("Failed to read a file: %s\n", strerror(errno));
136       goto cleanup;
137     }
138 
139     /* check magic */
140     if (memcmp(header.magic, SNAPPY_JAVA_MAGIC, SNAPPY_JAVA_MAGIC_LEN) != 0) {
141       print_error("This is not a snappy-java file.\n");
142       goto cleanup;
143     }
144   }
145 
146   /* check rest header */
147   header.version = SNZ_FROM_BE32(header.version);
148   if (header.version != SNAPPY_JAVA_FILE_VERSION) {
149     print_error("Unknown snappy-java version %d\n", header.version);
150     goto cleanup;
151   }
152 
153   header.compatible_version = SNZ_FROM_BE32(header.compatible_version);
154   if (header.compatible_version != SNAPPY_JAVA_FILE_VERSION) {
155     print_error("Unknown snappy-java compatible version %d\n", header.compatible_version);
156     goto cleanup;
157   }
158 
159   /* Use a file descriptor 'outfd' instead of the stdio file pointer 'outfp'
160    * to reduce the number of write system calls.
161    */
162   fflush(outfp);
163   outfd = fileno(outfp);
164 
165   /* read body */
166   work_buffer_init(&wb, DEFAULT_BLOCK_SIZE);
167   for (;;) {
168     /* read the compressed length in a block */
169     size_t compressed_length = 0;
170     size_t uncompressed_length = wb.uclen;
171     int idx;
172 
173     for (idx = 3; idx >= 0; idx--) {
174       int chr = getc(infp);
175       if (chr == -1) {
176         if (idx == 3) {
177           /* read all blocks */
178           err = 0;
179           goto cleanup;
180         }
181         print_error("Unexpected end of file.\n");
182         goto cleanup;
183       }
184       compressed_length |= (chr << (idx * 8));
185     }
186 
187     trace("read 4 bytes (compressed_length = %ld)\n", (long)compressed_length);
188     if (compressed_length == 0) {
189       print_error("Invalid compressed length %ld\n", (long)compressed_length);
190       goto cleanup;
191     }
192     if (compressed_length > wb.clen) {
193       work_buffer_resize(&wb, compressed_length, 0);
194     }
195 
196     /* read the compressed data */
197     if (fread(wb.c, compressed_length, 1, infp) != 1) {
198       if (feof(infp)) {
199         print_error("Unexpected end of file\n");
200       } else {
201         print_error("Failed to read a file: %s\n", strerror(errno));
202       }
203       goto cleanup;
204     }
205     trace("read %ld bytes.\n", (long)(compressed_length));
206 
207     /* check the uncompressed length */
208     err = snappy_uncompressed_length(wb.c, compressed_length, &uncompressed_length);
209     if (err != 0) {
210       print_error("Invalid data: GetUncompressedLength failed %d\n", err);
211       goto cleanup;
212     }
213     err = 1;
214     if (uncompressed_length > wb.uclen) {
215       work_buffer_resize(&wb, 0, uncompressed_length);
216     }
217 
218     /* uncompress and write */
219     if (snappy_uncompress(wb.c, compressed_length, wb.uc, &uncompressed_length)) {
220       print_error("Invalid data: RawUncompress failed\n");
221       goto cleanup;
222     }
223     if (write_full(outfd, wb.uc, uncompressed_length) != uncompressed_length) {
224       print_error("Failed to write a file: %s\n", strerror(errno));
225       goto cleanup;
226     }
227     trace("write %ld bytes\n", (long)uncompressed_length);
228   }
229  cleanup:
230   work_buffer_free(&wb);
231   return err;
232 }
233 
234 stream_format_t snappy_java_format = {
235   "snappy-java",
236   "https://github.com/xerial/snappy-java",
237   "snappy",
238   snappy_java_compress,
239   snappy_java_uncompress,
240 };
241