]> Sergey Matveev's repositories - tofuproxy.git/blob - cmd/zstd/unzstd.c
caddee5f6e4aebbef6822a77b42fe66f16bc6ac5
[tofuproxy.git] / cmd / zstd / unzstd.c
1 /*
2 unzstd -- .warc.zst decompressor
3 Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, version 3 of the License.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 // https://iipc.github.io/warc-specifications/specifications/warc-zstd/
19
20 #include <errno.h>
21 #include <inttypes.h>
22 #include <stdbool.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <sys/endian.h>
27
28 #include <zstd.h>
29
30 #ifdef __FreeBSD__
31 #include "capsicum.c.in"
32 #include <capsicum_helpers.h>
33 #include <err.h>
34 #include <sysexits.h>
35 #endif // __FreeBSD__
36
37 int
38 main(int argc, char **argv)
39 {
40     FILE *fdOff = fdopen(3, "wb");
41 #ifdef __FreeBSD__
42     if ((fdOff != NULL) && (caph_limit_stream(3, CAPH_WRITE)) != 0) {
43         errx(EX_OSERR, "can not caph_limit_stream(3)");
44     };
45     capsicum_start();
46 #endif // __FreeBSD__
47
48     ZSTD_DCtx *ctx = ZSTD_createDCtx();
49     if (ctx == NULL) {
50         fputs("can not initialize ZSTD_DCtx\n", stderr);
51         return 1;
52     };
53     int rc                 = EXIT_FAILURE;
54     uint8_t *bufIn         = NULL;
55     uint8_t *bufOut        = NULL;
56     const size_t bufInSize = ZSTD_DStreamInSize();
57     bufIn                  = malloc(bufInSize);
58     if (bufIn == NULL) {
59         fputs("no memory\n", stderr);
60         goto Exit;
61     };
62     const size_t bufOutSize = ZSTD_DStreamOutSize();
63     bufOut                  = malloc(bufOutSize);
64     if (bufOut == NULL) {
65         fputs("no memory\n", stderr);
66         goto Exit;
67     };
68
69     unsigned long long bufSize = 0;
70
71     ZSTD_inBuffer bIn   = {bufIn, 0, 0};
72     ZSTD_outBuffer bOut = {bufOut, 0, 0};
73
74     bool isEmpty      = true;
75     bool lastBlock    = false;
76     size_t n          = 0;
77     size_t written    = 0;
78     size_t offset     = 0;
79     size_t offsetPrev = 0;
80     size_t zCode      = 0;
81 ReadAgain:
82     for (;;) {
83         n = fread(bufIn, 1, bufInSize, stdin);
84         if (n != bufInSize) {
85             if (feof(stdin)) {
86                 lastBlock = true;
87             } else {
88                 perror("can not fread(FILE)");
89                 goto Exit;
90             };
91         };
92         if (n >= 8 && le32dec(bufIn) == 0x184D2A5D) {
93             // dictionary frame
94             size_t dictSize = (size_t)le32dec(bufIn + 4);
95             uint8_t *dict   = malloc(dictSize);
96             if (dict == NULL) {
97                 fprintf(stderr, "insufficient memory for dictionary: %zu\n", dictSize);
98                 goto Exit;
99             };
100             const size_t alreadyRead = n - 8;
101             memcpy(dict, bufIn + 8, alreadyRead);
102             errno = 0;
103             n     = fread(dict + alreadyRead, 1, dictSize - alreadyRead, stdin);
104             if (n != dictSize - alreadyRead) {
105                 perror("can not read dictionary data");
106                 free(dict);
107                 goto Exit;
108             };
109             offset     = dictSize + 8;
110             offsetPrev = offset;
111             if (fdOff != NULL) {
112                 fprintf(fdOff, "%zu\t0\n", offset);
113             };
114             uint32_t hdr = le32dec(dict);
115             switch (hdr) {
116             case ZSTD_MAGIC_DICTIONARY:
117                 zCode = ZSTD_DCtx_loadDictionary(ctx, dict, dictSize);
118                 free(dict);
119                 if ((zCode != 0) && (ZSTD_isError(zCode))) {
120                     fprintf(
121                         stderr,
122                         "can not load dictionary: %s\n",
123                         ZSTD_getErrorName(zCode));
124                     goto Exit;
125                 };
126                 goto ReadAgain;
127                 break;
128             case ZSTD_MAGICNUMBER:
129                 bufSize = ZSTD_getFrameContentSize(dict, dictSize);
130                 switch (bufSize) {
131                 case ZSTD_CONTENTSIZE_UNKNOWN:
132                 case ZSTD_CONTENTSIZE_ERROR:
133                     fprintf(stderr, "can not determine dictionary's size\n");
134                     free(dict);
135                     goto Exit;
136                 };
137                 uint8_t *buf = malloc(bufSize);
138                 if (buf == NULL) {
139                     fprintf(
140                         stderr, "insufficient memory for dictionary: %llu\n", bufSize);
141                     free(dict);
142                     goto Exit;
143                 };
144                 zCode = ZSTD_decompress(buf, bufSize, dict, dictSize);
145                 free(dict);
146                 if (ZSTD_isError(zCode)) {
147                     fprintf(
148                         stderr,
149                         "can not decompress dictionary: %s\n",
150                         ZSTD_getErrorName(zCode));
151                     free(buf);
152                     goto Exit;
153                 };
154                 zCode = ZSTD_DCtx_loadDictionary(ctx, buf, zCode);
155                 free(buf);
156                 if ((zCode != 0) && (ZSTD_isError(zCode))) {
157                     fprintf(
158                         stderr,
159                         "can not load dictionary: %s\n",
160                         ZSTD_getErrorName(zCode));
161                     goto Exit;
162                 };
163                 goto ReadAgain;
164                 break;
165             default:
166                 fprintf(stderr, "unknown dictionary header\n");
167                 free(dict);
168                 goto Exit;
169             };
170         };
171         isEmpty  = false;
172         bIn.size = n;
173         bIn.pos  = 0;
174         while (bIn.pos < bIn.size) {
175             bOut.size = bufOutSize;
176             bOut.pos  = 0;
177             zCode     = ZSTD_decompressStream(ctx, &bOut, &bIn);
178             if ((zCode != 0) && (ZSTD_isError(zCode))) {
179                 fprintf(stderr, "can not decompress: %s\n", ZSTD_getErrorName(zCode));
180                 goto Exit;
181             };
182             n = fwrite(bufOut, 1, bOut.pos, stdout);
183             if (n != bOut.pos) {
184                 perror("can not fwrite(stdout)");
185                 goto Exit;
186             };
187             written += n;
188             if (zCode == 0) {
189                 offset += bIn.pos;
190                 if (fdOff != NULL) {
191                     fprintf(fdOff, "%zu\t%zu\n", offset - offsetPrev, written);
192                 };
193                 offsetPrev = offset + bIn.pos;
194                 written    = 0;
195             };
196         };
197         if (lastBlock) {
198             break;
199         };
200         offset += bIn.pos;
201     };
202
203     if (isEmpty) {
204         fputs("empty input\n", stderr);
205         goto Exit;
206     };
207     if (zCode != 0) {
208         fprintf(stderr, "unfinished decompression: %s\n", ZSTD_getErrorName(zCode));
209         goto Exit;
210     };
211     rc = EXIT_SUCCESS;
212
213 Exit:
214     if (bufOut != NULL) {
215         free(bufOut);
216     };
217     if (bufIn != NULL) {
218         free(bufIn);
219     };
220     ZSTD_freeDCtx(ctx);
221     if ((fdOff != NULL) && (fclose(fdOff) != 0)) {
222         perror("can not fclose(4)");
223         return EXIT_FAILURE;
224     };
225     return rc;
226 };