]> Sergey Matveev's repositories - tofuproxy.git/blob - cmd/zstd/unzstd.c
faeda1559fa5c03ac740a177279fe4bf540537cc
[tofuproxy.git] / cmd / zstd / unzstd.c
1 /*
2 unzstd -- .warc.zst decompressor
3 Copyright (C) 2021-2023 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
27 #include <zstd.h>
28
29 #ifdef __FreeBSD__
30 #include "capsicum.c.in"
31 #include <capsicum_helpers.h>
32 #include <err.h>
33 #include <sysexits.h>
34 #endif // __FreeBSD__
35
36 static uint32_t
37 le32dec(const char buf[4])
38 {
39     return (uint32_t)(buf[3]) << 24 | (uint32_t)(buf[2]) << 16 |
40            (uint32_t)(buf[1]) << 8 | (uint32_t)(buf[0]);
41 }
42
43 int
44 main(int argc, char **argv)
45 {
46     FILE *fdOff = fdopen(3, "wb");
47 #ifdef __FreeBSD__
48     if ((fdOff != NULL) && (caph_limit_stream(3, CAPH_WRITE)) != 0) {
49         errx(EX_OSERR, "can not caph_limit_stream(3)");
50     }
51     capsicum_start();
52 #endif // __FreeBSD__
53
54     ZSTD_DCtx *ctx = ZSTD_createDCtx();
55     if (ctx == NULL) {
56         fputs("can not initialize ZSTD_DCtx\n", stderr);
57         return 1;
58     }
59     int rc = EXIT_FAILURE;
60     char *bufIn = NULL;
61     char *bufOut = NULL;
62     const size_t bufInSize = ZSTD_DStreamInSize();
63     bufIn = malloc(bufInSize);
64     if (bufIn == NULL) {
65         fputs("no memory\n", stderr);
66         goto Exit;
67     }
68     const size_t bufOutSize = ZSTD_DStreamOutSize();
69     bufOut = malloc(bufOutSize);
70     if (bufOut == NULL) {
71         fputs("no memory\n", stderr);
72         goto Exit;
73     }
74
75     unsigned long long bufSize = 0;
76
77     ZSTD_inBuffer bIn = {bufIn, 0, 0};
78     ZSTD_outBuffer bOut = {bufOut, 0, 0};
79
80     bool isEmpty = true;
81     bool lastBlock = false;
82     size_t n = 0;
83     size_t written = 0;
84     size_t offset = 0;
85     size_t offsetPrev = 0;
86     size_t zCode = 0;
87 ReadAgain:
88     for (;;) {
89         n = fread(bufIn, 1, bufInSize, stdin);
90         if (n != bufInSize) {
91             if (feof(stdin)) {
92                 lastBlock = true;
93             } else {
94                 perror("can not fread(FILE)");
95                 goto Exit;
96             }
97         }
98         if (n >= 8 && le32dec(bufIn) == 0x184D2A5D) {
99             // dictionary frame
100             size_t dictSize = (size_t)le32dec(bufIn + 4);
101             char *dict = malloc(dictSize);
102             if (dict == NULL) {
103                 fprintf(stderr, "insufficient memory for dictionary: %zu\n", dictSize);
104                 goto Exit;
105             }
106             const size_t alreadyRead = n - 8;
107             memcpy(dict, bufIn + 8, alreadyRead);
108             errno = 0;
109             n = fread(dict + alreadyRead, 1, dictSize - alreadyRead, stdin);
110             if (n != dictSize - alreadyRead) {
111                 perror("can not read dictionary data");
112                 free(dict);
113                 goto Exit;
114             }
115             offset = dictSize + 8;
116             offsetPrev = offset;
117             if (fdOff != NULL) {
118                 fprintf(fdOff, "%zu\t0\n", offset);
119             }
120             uint32_t hdr = le32dec(dict);
121             switch (hdr) {
122             case ZSTD_MAGIC_DICTIONARY:
123                 zCode = ZSTD_DCtx_loadDictionary(ctx, dict, dictSize);
124                 free(dict);
125                 if ((zCode != 0) && (ZSTD_isError(zCode))) {
126                     fprintf(
127                         stderr,
128                         "can not load dictionary: %s\n",
129                         ZSTD_getErrorName(zCode));
130                     goto Exit;
131                 }
132                 goto ReadAgain;
133             case ZSTD_MAGICNUMBER:
134                 bufSize = ZSTD_getFrameContentSize(dict, dictSize);
135                 switch (bufSize) {
136                 case ZSTD_CONTENTSIZE_UNKNOWN:
137                 case ZSTD_CONTENTSIZE_ERROR:
138                     fprintf(stderr, "can not determine dictionary's size\n");
139                     free(dict);
140                     goto Exit;
141                 }
142                 char *buf = malloc(bufSize);
143                 if (buf == NULL) {
144                     fprintf(
145                         stderr, "insufficient memory for dictionary: %llu\n", bufSize);
146                     free(dict);
147                     goto Exit;
148                 }
149                 zCode = ZSTD_decompress(buf, bufSize, dict, dictSize);
150                 free(dict);
151                 if (ZSTD_isError(zCode)) {
152                     fprintf(
153                         stderr,
154                         "can not decompress dictionary: %s\n",
155                         ZSTD_getErrorName(zCode));
156                     free(buf);
157                     goto Exit;
158                 }
159                 zCode = ZSTD_DCtx_loadDictionary(ctx, buf, zCode);
160                 free(buf);
161                 if ((zCode != 0) && (ZSTD_isError(zCode))) {
162                     fprintf(
163                         stderr,
164                         "can not load dictionary: %s\n",
165                         ZSTD_getErrorName(zCode));
166                     goto Exit;
167                 }
168                 goto ReadAgain;
169             default:
170                 fprintf(stderr, "unknown dictionary header\n");
171                 free(dict);
172                 goto Exit;
173             }
174         }
175         isEmpty = false;
176         bIn.size = n;
177         bIn.pos = 0;
178         while (bIn.pos < bIn.size) {
179             bOut.size = bufOutSize;
180             bOut.pos = 0;
181             zCode = ZSTD_decompressStream(ctx, &bOut, &bIn);
182             if ((zCode != 0) && (ZSTD_isError(zCode))) {
183                 fprintf(stderr, "can not decompress: %s\n", ZSTD_getErrorName(zCode));
184                 goto Exit;
185             }
186             n = fwrite(bufOut, 1, bOut.pos, stdout);
187             if (n != bOut.pos) {
188                 perror("can not fwrite(stdout)");
189                 goto Exit;
190             }
191             written += n;
192             if (zCode == 0) {
193                 offset += bIn.pos;
194                 if (fdOff != NULL) {
195                     fprintf(fdOff, "%zu\t%zu\n", offset - offsetPrev, written);
196                 }
197                 offsetPrev = offset + bIn.pos;
198                 written = 0;
199             }
200         }
201         if (lastBlock) {
202             break;
203         }
204         offset += bIn.pos;
205     }
206
207     if (isEmpty) {
208         fputs("empty input\n", stderr);
209         goto Exit;
210     }
211     if (zCode != 0) {
212         fprintf(stderr, "unfinished decompression: %s\n", ZSTD_getErrorName(zCode));
213         goto Exit;
214     }
215     rc = EXIT_SUCCESS;
216
217 Exit:
218     if (bufOut != NULL) {
219         free(bufOut);
220     }
221     if (bufIn != NULL) {
222         free(bufIn);
223     }
224     ZSTD_freeDCtx(ctx);
225     if ((fdOff != NULL) && (fclose(fdOff) != 0)) {
226         perror("can not fclose(4)");
227         return EXIT_FAILURE;
228     }
229     return rc;
230 }