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