]>
Commit | Line | Data |
---|---|---|
c352b776 MT |
1 | /*############################################################################# |
2 | # # | |
3 | # Pakfire - The IPFire package management system # | |
4 | # Copyright (C) 2021 Pakfire development team # | |
5 | # # | |
6 | # This program is free software: you can redistribute it and/or modify # | |
7 | # it under the terms of the GNU General Public License as published by # | |
8 | # the Free Software Foundation, either version 3 of the License, or # | |
9 | # (at your option) any later version. # | |
10 | # # | |
11 | # This program is distributed in the hope that it will be useful, # | |
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # | |
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # | |
14 | # GNU General Public License for more details. # | |
15 | # # | |
16 | # You should have received a copy of the GNU General Public License # | |
17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. # | |
18 | # # | |
19 | #############################################################################*/ | |
20 | ||
64f977a3 | 21 | #include <errno.h> |
c352b776 | 22 | #include <stdio.h> |
99018421 | 23 | #include <stdlib.h> |
c352b776 MT |
24 | #include <string.h> |
25 | ||
26 | #include <lzma.h> | |
9476d502 | 27 | #include <zstd.h> |
c352b776 MT |
28 | |
29 | #include <pakfire/compress.h> | |
30 | ||
31 | // Read up to N bytes for analyze the magic | |
32 | #define MAX_MAGIC_LENGTH 6 | |
33 | ||
5cd454df MT |
34 | // Compression/Decompression buffer size |
35 | #define BUFFER_SIZE 64 * 1024 | |
36 | ||
0bed1e1d MT |
37 | // Settings for XZ |
38 | #define XZ_COMPRESSION_LEVEL 6 | |
39 | ||
9476d502 MT |
40 | // Settings for ZSTD |
41 | #define ZSTD_COMPRESSION_LEVEL 7 | |
42 | ||
c352b776 MT |
43 | const struct compressor { |
44 | char magic[MAX_MAGIC_LENGTH]; | |
45 | size_t magic_length; | |
46 | FILE* (*open)(FILE* f, const char* mode); | |
47 | } compressors[] = { | |
48 | // XZ | |
49 | { { 0xFD, '7', 'z', 'X', 'Z', 0x00 }, 6, pakfire_xzfopen, }, | |
60732b6e MT |
50 | // ZSTD |
51 | { { 0x28, 0xb5, 0x2f, 0xfd }, 4, pakfire_zstdfopen, }, | |
52 | // End | |
c352b776 MT |
53 | { "", 0, NULL, }, |
54 | }; | |
55 | ||
c352b776 MT |
56 | // Try to guess the compression |
57 | FILE* pakfire_xfopen(FILE* f, const char* mode) { | |
58 | char buffer[MAX_MAGIC_LENGTH]; | |
59 | ||
64f977a3 MT |
60 | if (!f) { |
61 | errno = EBADFD; | |
c352b776 | 62 | return NULL; |
64f977a3 MT |
63 | } |
64 | ||
65 | if (!mode) { | |
66 | errno = EINVAL; | |
67 | return NULL; | |
68 | } | |
69 | ||
70 | // This only works for reading files | |
71 | if (*mode != 'r') { | |
72 | errno = ENOTSUP; | |
73 | return NULL; | |
74 | } | |
c352b776 MT |
75 | |
76 | fpos_t pos; | |
77 | ||
78 | // Store the position | |
79 | int r = fgetpos(f, &pos); | |
80 | if (r < 0) | |
81 | return NULL; | |
82 | ||
83 | // Read a couple of bytes | |
84 | size_t bytes_read = fread(buffer, 1, sizeof(buffer), f); | |
85 | ||
86 | // Reset position | |
87 | r = fsetpos(f, &pos); | |
88 | if (r < 0) | |
89 | return NULL; | |
90 | ||
91 | // Check if we could read anything | |
92 | if (!bytes_read || bytes_read < sizeof(buffer)) | |
93 | return f; | |
94 | ||
95 | // Analyze magic | |
96 | for (const struct compressor* c = compressors; c->open; c++) { | |
97 | // Check if we have read enough data | |
98 | if (bytes_read < c->magic_length) | |
99 | continue; | |
100 | ||
101 | // Compare the magic value | |
102 | r = memcmp(c->magic, buffer, c->magic_length); | |
103 | if (r) | |
104 | continue; | |
105 | ||
106 | // We found a match! | |
107 | return c->open(f, mode); | |
108 | } | |
109 | ||
110 | // Nothing seems to match | |
49492077 | 111 | errno = ENOTSUP; |
c352b776 MT |
112 | return f; |
113 | } | |
114 | ||
5cd454df MT |
115 | struct xz_cookie { |
116 | FILE* f; | |
117 | char mode; | |
118 | lzma_stream stream; | |
119 | int done; | |
120 | ||
121 | uint8_t buffer[BUFFER_SIZE]; | |
122 | }; | |
123 | ||
c352b776 MT |
124 | static ssize_t xz_read(void* data, char* buffer, size_t size) { |
125 | struct xz_cookie* cookie = (struct xz_cookie*)data; | |
126 | if (!cookie) | |
127 | return -1; | |
128 | ||
48187ff3 MT |
129 | // Do not read when mode is "w" |
130 | if (cookie->mode == 'w') | |
6923552a | 131 | return -1; |
48187ff3 | 132 | |
c352b776 MT |
133 | lzma_action action = LZMA_RUN; |
134 | ||
135 | // Set output to allocated buffer | |
136 | cookie->stream.next_out = (uint8_t *)buffer; | |
137 | cookie->stream.avail_out = size; | |
138 | ||
139 | while (1) { | |
140 | // Read something when the input buffer is empty | |
141 | if (cookie->stream.avail_in == 0) { | |
142 | cookie->stream.next_in = cookie->buffer; | |
143 | cookie->stream.avail_in = fread(cookie->buffer, | |
5cd454df | 144 | 1, sizeof(cookie->buffer), cookie->f); |
c352b776 MT |
145 | |
146 | // Break if the input file could not be read | |
147 | if (ferror(cookie->f)) | |
148 | return -1; | |
149 | ||
150 | // Finish after we have reached the end of the input file | |
5e12758c | 151 | if (feof(cookie->f)) |
c352b776 | 152 | cookie->done = 1; |
c352b776 MT |
153 | } |
154 | ||
155 | lzma_ret ret = lzma_code(&cookie->stream, action); | |
156 | ||
157 | // If the stream has ended, we just send the | |
158 | // remaining output and mark that we are done. | |
159 | if (ret == LZMA_STREAM_END) { | |
160 | cookie->done = 1; | |
161 | return size - cookie->stream.avail_out; | |
162 | } | |
163 | ||
164 | // Break on all other unexpected errors | |
165 | if (ret != LZMA_OK) | |
166 | return -1; | |
167 | ||
168 | // When we have read enough to fill the entire output buffer, we return | |
169 | if (cookie->stream.avail_out == 0) | |
170 | return size; | |
171 | ||
172 | if (cookie->done) | |
173 | return -1; | |
174 | } | |
175 | } | |
176 | ||
0bed1e1d MT |
177 | static ssize_t xz_write(void* data, const char* buffer, size_t size) { |
178 | struct xz_cookie* cookie = (struct xz_cookie*)data; | |
179 | if (!cookie) | |
180 | return -1; | |
181 | ||
182 | // Do not write when mode is "r" | |
183 | if (cookie->mode == 'r') | |
6923552a | 184 | return -1; |
0bed1e1d MT |
185 | |
186 | // Return nothing when there is no input | |
187 | if (size == 0) | |
188 | return 0; | |
189 | ||
190 | // Set input to allocated buffer | |
9d816df3 MT |
191 | cookie->stream.next_in = (uint8_t *)buffer; |
192 | cookie->stream.avail_in = size; | |
0bed1e1d MT |
193 | |
194 | while (1) { | |
195 | cookie->stream.next_out = cookie->buffer; | |
196 | cookie->stream.avail_out = sizeof(cookie->buffer); | |
197 | ||
198 | lzma_ret ret = lzma_code(&cookie->stream, LZMA_RUN); | |
199 | if (ret != LZMA_OK) | |
200 | return -1; | |
201 | ||
202 | size_t bytes_to_write = sizeof(cookie->buffer) - cookie->stream.avail_out; | |
203 | if (bytes_to_write) { | |
204 | size_t bytes_written = fwrite(cookie->buffer, 1, bytes_to_write, cookie->f); | |
205 | ||
206 | if (bytes_written != bytes_to_write) | |
207 | return -1; | |
208 | } | |
209 | ||
210 | // Report that all data has been written | |
211 | if (cookie->stream.avail_in == 0) | |
212 | return size; | |
213 | } | |
214 | } | |
215 | ||
c352b776 MT |
216 | static int xz_close(void* data) { |
217 | struct xz_cookie* cookie = (struct xz_cookie*)data; | |
99018421 MT |
218 | if (!cookie) |
219 | return -1; | |
220 | ||
221 | if (cookie->mode == 'w') { | |
222 | while (1) { | |
223 | cookie->stream.next_out = cookie->buffer; | |
224 | cookie->stream.avail_out = sizeof(cookie->buffer); | |
225 | ||
226 | lzma_ret ret = lzma_code(&cookie->stream, LZMA_FINISH); | |
227 | if (ret != LZMA_OK && ret != LZMA_STREAM_END) | |
228 | return -1; | |
229 | ||
230 | size_t bytes_to_write = sizeof(cookie->buffer) - cookie->stream.avail_out; | |
231 | if (bytes_to_write) { | |
232 | size_t bytes_written = fwrite(cookie->buffer, 1, bytes_to_write, cookie->f); | |
233 | ||
234 | if (bytes_written != bytes_to_write) | |
235 | return -1; | |
236 | } | |
237 | ||
238 | if (ret == LZMA_STREAM_END) | |
239 | break; | |
240 | } | |
241 | } | |
c352b776 | 242 | |
c352b776 MT |
243 | lzma_end(&cookie->stream); |
244 | ||
245 | // Close input file | |
99018421 MT |
246 | int r = fclose(cookie->f); |
247 | free(cookie); | |
c352b776 | 248 | |
99018421 | 249 | return r; |
c352b776 MT |
250 | } |
251 | ||
99018421 MT |
252 | static cookie_io_functions_t xz_functions = { |
253 | .read = xz_read, | |
254 | .write = xz_write, | |
255 | .seek = NULL, | |
256 | .close = xz_close, | |
257 | }; | |
258 | ||
c352b776 | 259 | FILE* pakfire_xzfopen(FILE* f, const char* mode) { |
99018421 MT |
260 | lzma_ret ret; |
261 | ||
48187ff3 MT |
262 | if (!f) { |
263 | errno = EBADFD; | |
264 | return NULL; | |
265 | } | |
266 | ||
267 | if (!mode) { | |
268 | errno = EINVAL; | |
269 | return NULL; | |
270 | } | |
271 | ||
99018421 MT |
272 | struct xz_cookie* cookie = calloc(1, sizeof(*cookie)); |
273 | if (!cookie) | |
274 | return NULL; | |
c352b776 | 275 | |
99018421 MT |
276 | cookie->f = f; |
277 | cookie->mode = *mode; | |
0bed1e1d | 278 | |
99018421 | 279 | switch (cookie->mode) { |
0bed1e1d | 280 | case 'r': |
99018421 | 281 | ret = lzma_stream_decoder(&cookie->stream, UINT64_MAX, 0); |
0bed1e1d MT |
282 | break; |
283 | ||
284 | case 'w': | |
99018421 | 285 | ret = lzma_easy_encoder(&cookie->stream, XZ_COMPRESSION_LEVEL, LZMA_CHECK_SHA256); |
0bed1e1d MT |
286 | break; |
287 | ||
288 | default: | |
289 | errno = ENOTSUP; | |
2d4af759 | 290 | goto ERROR; |
0bed1e1d MT |
291 | } |
292 | ||
c352b776 | 293 | if (ret != LZMA_OK) |
2d4af759 | 294 | goto ERROR; |
c352b776 | 295 | |
99018421 | 296 | return fopencookie(cookie, mode, xz_functions); |
2d4af759 MT |
297 | |
298 | ERROR: | |
299 | free(cookie); | |
300 | return NULL; | |
c352b776 | 301 | } |
9476d502 MT |
302 | |
303 | // ZSTD | |
304 | ||
305 | struct zstd_cookie { | |
306 | FILE* f; | |
307 | char mode; | |
308 | int done; | |
309 | ||
310 | // Encoder | |
311 | ZSTD_CStream* cstream; | |
312 | ZSTD_inBuffer in; | |
313 | ||
314 | // Decoder | |
315 | ZSTD_DStream* dstream; | |
316 | ZSTD_outBuffer out; | |
317 | ||
318 | uint8_t buffer[BUFFER_SIZE]; | |
319 | }; | |
320 | ||
321 | static ssize_t zstd_read(void* data, char* buffer, size_t size) { | |
322 | struct zstd_cookie* cookie = (struct zstd_cookie*)data; | |
323 | if (!cookie) | |
324 | return -1; | |
325 | ||
326 | // Do not read when mode is "w" | |
327 | if (cookie->mode == 'w') | |
328 | return -1; | |
329 | ||
330 | if (cookie->done) | |
331 | return 0; | |
332 | ||
333 | size_t r = 0; | |
334 | ||
335 | // Configure output buffer | |
336 | cookie->out.dst = buffer; | |
337 | cookie->out.pos = 0; | |
338 | cookie->out.size = size; | |
339 | ||
340 | while (1) { | |
341 | if (!feof(cookie->f) && (cookie->in.pos == cookie->in.size)) { | |
342 | cookie->in.pos = 0; | |
343 | cookie->in.size = fread(cookie->buffer, 1, sizeof(cookie->buffer), cookie->f); | |
344 | } | |
345 | ||
346 | if (r || cookie->in.size) | |
347 | r = ZSTD_decompressStream(cookie->dstream, &cookie->out, &cookie->in); | |
348 | ||
349 | if (r == 0 && feof(cookie->f)) { | |
350 | cookie->done = 1; | |
351 | return cookie->out.pos; | |
352 | } | |
353 | ||
354 | if (ZSTD_isError(r)) | |
355 | return -1; | |
356 | ||
357 | // Buffer full | |
358 | if (cookie->out.pos == size) | |
359 | return size; | |
360 | } | |
361 | } | |
362 | ||
99a1527a MT |
363 | static ssize_t zstd_write(void* data, const char* buffer, size_t size) { |
364 | struct zstd_cookie* cookie = (struct zstd_cookie*)data; | |
365 | if (!cookie) | |
366 | return -1; | |
367 | ||
368 | // Do not write when mode is "r" | |
369 | if (cookie->mode == 'r') | |
370 | return -1; | |
371 | ||
372 | // Return nothing when there is no input | |
373 | if (size == 0) | |
374 | return 0; | |
375 | ||
376 | // Configure input buffer | |
377 | cookie->in.src = buffer; | |
378 | cookie->in.pos = 0; | |
379 | cookie->in.size = size; | |
380 | ||
381 | while (1) { | |
382 | cookie->out.pos = 0; | |
383 | ||
384 | size_t r = ZSTD_compressStream(cookie->cstream, &cookie->out, &cookie->in); | |
385 | if (ZSTD_isError(r)) | |
386 | return -1; | |
387 | ||
388 | if (cookie->out.pos > 0) { | |
389 | size_t bytes_written = fwrite(cookie->buffer, 1, cookie->out.pos, cookie->f); | |
390 | ||
391 | if (bytes_written != cookie->out.pos) | |
392 | return -1; | |
393 | } | |
394 | ||
395 | // Return when all input has been written | |
396 | if (cookie->in.pos == size) | |
397 | return size; | |
398 | } | |
399 | } | |
400 | ||
9476d502 MT |
401 | static int zstd_close(void* data) { |
402 | struct zstd_cookie* cookie = (struct zstd_cookie*)data; | |
403 | if (!cookie) | |
404 | return -1; | |
405 | ||
99a1527a MT |
406 | if (cookie->mode == 'w') { |
407 | while (1) { | |
408 | // Reset output buffer | |
409 | cookie->out.pos = 0; | |
410 | ||
411 | size_t r = ZSTD_endStream(cookie->cstream, &cookie->out); | |
412 | if (ZSTD_isError(r)) | |
413 | return -1; | |
414 | ||
415 | if (cookie->out.pos > 0) { | |
416 | size_t bytes_written = fwrite(cookie->buffer, 1, cookie->out.pos, cookie->f); | |
417 | ||
418 | if (bytes_written != cookie->out.pos) | |
419 | return -1; | |
420 | } | |
421 | ||
422 | if (r == 0) | |
423 | break; | |
424 | } | |
425 | } | |
9476d502 MT |
426 | |
427 | int r = fclose(cookie->f); | |
428 | ||
429 | // Free everything | |
430 | if (cookie->cstream) | |
431 | ZSTD_freeCStream(cookie->cstream); | |
432 | if (cookie->dstream) | |
433 | ZSTD_freeDStream(cookie->dstream); | |
434 | free(cookie); | |
435 | ||
436 | return r; | |
437 | } | |
438 | ||
439 | static cookie_io_functions_t zstd_functions = { | |
440 | .read = zstd_read, | |
99a1527a | 441 | .write = zstd_write, |
9476d502 MT |
442 | .seek = NULL, |
443 | .close = zstd_close, | |
444 | }; | |
445 | ||
446 | FILE* pakfire_zstdfopen(FILE* f, const char* mode) { | |
447 | if (!f) { | |
448 | errno = EBADFD; | |
449 | return NULL; | |
450 | } | |
451 | ||
452 | if (!mode) { | |
453 | errno = EINVAL; | |
454 | return NULL; | |
455 | } | |
456 | ||
457 | struct zstd_cookie* cookie = calloc(1, sizeof(*cookie)); | |
458 | if (!cookie) | |
459 | return NULL; | |
460 | ||
461 | cookie->f = f; | |
462 | cookie->mode = *mode; | |
463 | ||
464 | size_t r; | |
465 | switch (cookie->mode) { | |
466 | case 'r': | |
467 | // Allocate stream | |
468 | cookie->dstream = ZSTD_createDStream(); | |
469 | if (!cookie->dstream) | |
470 | goto ERROR; | |
471 | ||
472 | // Initialize stream | |
473 | r = ZSTD_initDStream(cookie->dstream); | |
474 | if (ZSTD_isError(r)) | |
475 | goto ERROR; | |
476 | ||
477 | cookie->in.src = cookie->buffer; | |
478 | cookie->in.pos = 0; | |
479 | cookie->in.size = 0; | |
480 | break; | |
481 | ||
482 | case 'w': | |
483 | // Allocate stream | |
484 | cookie->cstream = ZSTD_createCStream(); | |
485 | if (!cookie->cstream) | |
486 | goto ERROR; | |
487 | ||
488 | // Initialize stream | |
489 | r = ZSTD_initCStream(cookie->cstream, ZSTD_COMPRESSION_LEVEL); | |
490 | if (ZSTD_isError(r)) | |
491 | goto ERROR; | |
492 | ||
493 | cookie->out.dst = cookie->buffer; | |
494 | cookie->out.pos = 0; | |
495 | cookie->out.size = sizeof(cookie->buffer); | |
496 | break; | |
497 | ||
498 | default: | |
499 | errno = ENOTSUP; | |
500 | goto ERROR; | |
501 | } | |
502 | ||
503 | return fopencookie(cookie, mode, zstd_functions); | |
504 | ||
505 | ERROR: | |
506 | if (cookie->cstream) | |
507 | ZSTD_freeCStream(cookie->cstream); | |
508 | if (cookie->dstream) | |
509 | ZSTD_freeDStream(cookie->dstream); | |
510 | free(cookie); | |
511 | ||
512 | return NULL; | |
513 | } |