blob: 1ffe62a2b01678139d093697287b750b08a0631f [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <lz4/lz4frame.h>
#define BLOCK_SIZE 65536
#define WR_NEWFILE O_WRONLY | O_CREAT | O_TRUNC
#define PERM_644 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
static void usage(const char* arg0) {
printf("usage: %s [-1|-9] [-d] <input file> <output file>\n", arg0);
printf(" -1 fast compression (default)\n");
printf(" -9 high compression (slower)\n");
printf(" -d decompress\n");
}
static int do_decompress(const char* infile, const char* outfile) {
int infd, outfd;
infd = open(infile, O_RDONLY);
if (infd < 0) {
fprintf(stderr, "could not open %s: %s\n", infile, strerror(errno));
return -1;
}
outfd = open(outfile, WR_NEWFILE, PERM_644);
if (outfd < 0) {
fprintf(stderr, "could not open %s: %s\n", outfile, strerror(errno));
close(infd);
return -1;
}
LZ4F_decompressionContext_t dctx;
LZ4F_errorCode_t errc = LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION);
if (LZ4F_isError(errc)) {
fprintf(stderr, "could not initialize decompression: %s\n", LZ4F_getErrorName(errc));
close(outfd);
close(infd);
return -1;
}
uint8_t inbuf[BLOCK_SIZE];
uint8_t outbuf[BLOCK_SIZE];
// Read first 4 bytes to let LZ4 tell us how much it expects in the first
// pass.
size_t src_sz = 4;
size_t dst_sz = 0;
ssize_t nr = read(infd, inbuf, src_sz);
if (nr < (ssize_t)src_sz) {
fprintf(stderr, "could not read from %s", infile);
if (nr < 0) {
fprintf(stderr, ": %s", strerror(errno));
}
fprintf(stderr, "\n");
goto done;
}
size_t to_read = LZ4F_decompress(dctx, outbuf, &dst_sz, inbuf, &src_sz, NULL);
if (LZ4F_isError(to_read)) {
fprintf(stderr, "could not decompress %s: %s\n", infile, LZ4F_getErrorName(to_read));
goto done;
}
if (to_read > BLOCK_SIZE) {
to_read = BLOCK_SIZE;
}
while ((nr = read(infd, inbuf, to_read)) > 0) {
src_sz = nr;
ssize_t pos = 0;
size_t next = 0;
while (pos < nr) {
dst_sz = BLOCK_SIZE;
next = LZ4F_decompress(dctx, outbuf, &dst_sz, inbuf + pos, &src_sz, NULL);
if (LZ4F_isError(next)) {
fprintf(stderr, "could not decompress %s: %s\n", infile, LZ4F_getErrorName(to_read));
goto done;
}
if (dst_sz) {
ssize_t nw = write(outfd, outbuf, dst_sz);
if (nw != (ssize_t)dst_sz) {
fprintf(stderr, "could not write to %s", outfile);
if (nw < 0) {
fprintf(stderr, ": %s", strerror(errno));
}
fprintf(stderr, "\n");
goto done;
}
}
pos += src_sz;
src_sz = nr - pos;
}
to_read = next;
if (to_read > BLOCK_SIZE || to_read == 0) {
to_read = BLOCK_SIZE;
}
}
if (nr < 0) {
fprintf(stderr, "error reading %s: %s\n", infile, strerror(errno));
goto done;
}
done:
LZ4F_freeDecompressionContext(dctx);
close(outfd);
close(infd);
return 0;
}
static int do_compress(const char* infile, const char* outfile, int clevel) {
int infd, outfd;
infd = open(infile, O_RDONLY);
if (infd < 0) {
fprintf(stderr, "could not open %s: %s\n", infile, strerror(errno));
return -1;
}
outfd = open(outfile, WR_NEWFILE, PERM_644);
if (outfd < 0) {
fprintf(stderr, "could not open %s: %s\n", outfile, strerror(errno));
close(infd);
return -1;
}
LZ4F_preferences_t prefs;
memset(&prefs, 0, sizeof(prefs));
prefs.compressionLevel = clevel;
uint8_t inbuf[BLOCK_SIZE];
size_t outsize = LZ4F_compressFrameBound(BLOCK_SIZE, &prefs);
uint8_t* outbuf = malloc(outsize);
if (!outbuf) {
fprintf(stderr, "out of memory\n");
close(outfd);
close(infd);
return ENOMEM;
}
// TODO: do the whole file in one frame, using the LZ4F begin/update/end
// functions.
ssize_t nr = 0;
while ((nr = read(infd, inbuf, BLOCK_SIZE)) > 0) {
ssize_t csz = LZ4F_compressFrame(outbuf, outsize, inbuf, nr, &prefs);
if (LZ4F_isError(csz)) {
fprintf(stderr, "error compressing %s: %s\n", infile, LZ4F_getErrorName(csz));
goto done;
}
ssize_t nw = write(outfd, outbuf, csz);
if (nw != csz) {
fprintf(stderr, "could not write to %s", outfile);
if (nw < 0) {
fprintf(stderr, ": %s", strerror(errno));
}
fprintf(stderr, "\n");
goto done;
}
}
if (nr < 0) {
fprintf(stderr, "error reading %s: %s\n", infile, strerror(errno));
goto done;
}
done:
free(outbuf);
close(outfd);
close(infd);
return 0;
}
int main(int argc, char* argv[]) {
int clevel = 1;
bool decompress = false;
const char* infile = NULL;
const char* outfile = NULL;
for (int i = 1; i < argc; i++) {
if (!strcmp("-d", argv[i])) {
decompress = true;
continue;
}
if (!strcmp("-9", argv[i])) {
clevel = 9;
continue;
}
if (!strcmp("-h", argv[i])) {
usage(argv[0]);
return 0;
}
if (!infile) {
infile = argv[i];
continue;
}
if (!outfile) {
outfile = argv[i];
continue;
}
fprintf(stderr, "Unknown argument: %s\n", argv[i]);
usage(argv[0]);
return -1;
}
if (!infile || !outfile) {
usage(argv[0]);
return 0;
}
printf("%scompressing %s into %s", decompress ? "de" : "", infile, outfile);
if (!decompress) {
printf(" at level %d", clevel);
}
printf("\n");
if (decompress) {
return do_decompress(infile, outfile);
} else {
return do_compress(infile, outfile, clevel);
}
}