#include #include #include "kexec-lzma.h" #include "config.h" #include "kexec.h" #ifdef HAVE_LIBLZMA #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #define kBufferSize (1 << 15) typedef struct lzfile { uint8_t buf[kBufferSize]; lzma_stream strm; FILE *file; int encoding; int eof; } LZFILE; LZFILE *lzopen(const char *path, const char *mode); int lzclose(LZFILE *lzfile); ssize_t lzread(LZFILE *lzfile, void *buf, size_t len); static LZFILE *lzopen_internal(const char *path, const char *mode, int fd) { int level = 5; int encoding = 0; FILE *fp; LZFILE *lzfile; lzma_ret ret; lzma_stream lzma_strm_tmp = LZMA_STREAM_INIT; for (; *mode; mode++) { if (*mode == 'w') encoding = 1; else if (*mode == 'r') encoding = 0; else if (*mode >= '1' && *mode <= '9') level = *mode - '0'; } if (fd != -1) fp = fdopen(fd, encoding ? "w" : "r"); else fp = fopen(path, encoding ? "w" : "r"); if (!fp) return NULL; lzfile = calloc(1, sizeof(*lzfile)); if (!lzfile) { fclose(fp); return NULL; } lzfile->file = fp; lzfile->encoding = encoding; lzfile->eof = 0; lzfile->strm = lzma_strm_tmp; if (encoding) { lzma_options_lzma opt_lzma; if (lzma_lzma_preset(&opt_lzma, level - 1)) return NULL; ret = lzma_alone_encoder(&lzfile->strm, &opt_lzma); } else { ret = lzma_auto_decoder(&lzfile->strm, UINT64_C(64) * 1024 * 1024, 0); } if (ret != LZMA_OK) { fclose(fp); free(lzfile); return NULL; } return lzfile; } LZFILE *lzopen(const char *path, const char *mode) { return lzopen_internal(path, mode, -1); } int lzclose(LZFILE *lzfile) { lzma_ret ret; size_t n; if (!lzfile) return -1; if (lzfile->encoding) { for (;;) { lzfile->strm.avail_out = kBufferSize; lzfile->strm.next_out = lzfile->buf; ret = lzma_code(&lzfile->strm, LZMA_FINISH); if (ret != LZMA_OK && ret != LZMA_STREAM_END) return -1; n = kBufferSize - lzfile->strm.avail_out; if (n && fwrite(lzfile->buf, 1, n, lzfile->file) != n) return -1; if (ret == LZMA_STREAM_END) break; } } lzma_end(&lzfile->strm); return fclose(lzfile->file); free(lzfile); } ssize_t lzread(LZFILE *lzfile, void *buf, size_t len) { lzma_ret ret; int eof = 0; if (!lzfile || lzfile->encoding) return -1; if (lzfile->eof) return 0; lzfile->strm.next_out = buf; lzfile->strm.avail_out = len; for (;;) { if (!lzfile->strm.avail_in) { lzfile->strm.next_in = lzfile->buf; lzfile->strm.avail_in = fread(lzfile->buf, 1, kBufferSize, lzfile->file); if (!lzfile->strm.avail_in) eof = 1; } ret = lzma_code(&lzfile->strm, LZMA_RUN); if (ret == LZMA_STREAM_END) { lzfile->eof = 1; return len - lzfile->strm.avail_out; } if (ret != LZMA_OK) return -1; if (!lzfile->strm.avail_out) return len; if (eof) return -1; } } int is_lzma_file(const char *filename) { FILE *fp; int ret = 0; uint8_t buf[13]; if (!filename) return 0; fp = fopen(filename, "r"); if (fp == NULL) return 0; const size_t size = fread(buf, 1, sizeof(buf), fp); if (size != 13) { /* file is too small to be a lzma file. */ fclose(fp); return 0; } lzma_filter filter = { .id = LZMA_FILTER_LZMA1 }; switch (lzma_properties_decode(&filter, NULL, buf, 5)) { case LZMA_OK: ret = 1; break; default: /* It's not a lzma file */ ret = 0; } fclose(fp); return ret; } char *lzma_decompress_file(const char *filename, off_t *r_size) { LZFILE *fp; char *buf; off_t size, allocated; ssize_t result; dbgprintf("Try LZMA decompression.\n"); *r_size = 0; if (!filename) return NULL; if (!is_lzma_file(filename)) return NULL; fp = lzopen(filename, "rb"); if (fp == 0) { dbgprintf("Cannot open `%s'\n", filename); return NULL; } size = 0; allocated = 65536; buf = xmalloc(allocated); do { if (size == allocated) { allocated <<= 1; buf = xrealloc(buf, allocated); } result = lzread(fp, buf + size, allocated - size); if (result < 0) { if ((errno == EINTR) || (errno == EAGAIN)) continue; dbgprintf("%s: read on %s of %ld bytes failed\n", __func__, filename, (allocated - size) + 0UL); break; } size += result; } while (result > 0); if (lzclose(fp) != LZMA_OK) { dbgprintf("%s: Close of %s failed\n", __func__, filename); goto fail; } if (result < 0) goto fail; *r_size = size; return buf; fail: free(buf); return NULL; } #else char *lzma_decompress_file(const char *UNUSED(filename), off_t *UNUSED(r_size)) { return NULL; } #endif /* HAVE_LIBLZMA */