Current section
Files
Jump to
Current section
Files
src/bzip2.c
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <bzlib.h>
#include <erl_nif.h>
// TODO: Parameterize. This has been tuned so that the decompression NIF calls
// return in less than 1ms on a developer's machine, but mileage may vary, and
// compression hasn't been tuned at all.
static size_t default_output_buffer_size = 10 * 1024;
// Uncomment for verbose output.
// #define BZIP2_EX_DEBUG
#ifdef BZIP2_EX_DEBUG
static int verbosity = 4;
static void debug_print(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
}
#else
static int verbosity = 0;
static inline void debug_print(const char* fmt, ...) {}
#endif
static void* bzalloc_erl(void* opaque, int num, int unit_size) {
return enif_alloc(num * unit_size);
}
void bzfree_erl(void* opaque, void* ptr) {
enif_free(ptr);
}
// An opaque structure passed back to elixir, to support continuation.
static ErlNifResourceType *erl_bz_stream_wrapper;
typedef struct {
void* input_buffer;
void* output_buffer;
bz_stream* stream;
} bz_stream_wrapper;
static ERL_NIF_TERM atom_ok;
static ERL_NIF_TERM atom_cont;
static ERL_NIF_TERM atom_next_input;
static ERL_NIF_TERM atom_stream_end;
static ERL_NIF_TERM atom_bad_magic;
static ERL_NIF_TERM atom_sequence_error;
static ERL_NIF_TERM atom_param_error;
static ERL_NIF_TERM atom_mem_error;
static ERL_NIF_TERM atom_data_error;
static ERL_NIF_TERM atom_io_error;
static ERL_NIF_TERM atom_unexpected_eof;
static ERL_NIF_TERM atom_outbuff_full;
static ERL_NIF_TERM atom_config_error;
static ERL_NIF_TERM atom_unknown_error;
static ERL_NIF_TERM
convert_error_to_atom(int bz2_error) {
switch (bz2_error) {
case BZ_SEQUENCE_ERROR: return atom_sequence_error;
case BZ_PARAM_ERROR: return atom_param_error;
case BZ_MEM_ERROR: return atom_mem_error;
case BZ_DATA_ERROR: return atom_data_error;
case BZ_IO_ERROR: return atom_io_error;
case BZ_UNEXPECTED_EOF: return atom_unexpected_eof;
case BZ_OUTBUFF_FULL: return atom_outbuff_full;
case BZ_CONFIG_ERROR: return atom_config_error;
default: return atom_unknown_error;
}
}
static void
destroy_stream_wrapper(ErlNifEnv *env, void *res) {
bz_stream_wrapper** resource_ptr = res;
bz_stream_wrapper* resource = *resource_ptr;
if (resource != NULL) {
if (resource->input_buffer != NULL) {
enif_free(resource->input_buffer);
}
if (resource->output_buffer != NULL) {
enif_free(resource->output_buffer);
}
if (resource->stream != NULL) {
enif_free(resource->stream);
}
}
}
static bz_stream_wrapper*
allocate_stream_wrapper(ErlNifEnv *env) {
bz_stream_wrapper* state = enif_alloc_resource(erl_bz_stream_wrapper, sizeof(bz_stream_wrapper));
state->stream = enif_alloc(sizeof(bz_stream));
if (state->stream == NULL) {
destroy_stream_wrapper(env, &state);
return NULL;
}
state->input_buffer = NULL;
state->output_buffer = enif_alloc(default_output_buffer_size);
if (state->output_buffer == NULL) {
destroy_stream_wrapper(env, &state);
return NULL;
}
state->stream->bzalloc = &bzalloc_erl;
state->stream->bzfree = &bzfree_erl;
state->stream->opaque = NULL;
return state;
}
static ERL_NIF_TERM
compress_init(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
bz_stream_wrapper* state = allocate_stream_wrapper(env);
if (state == NULL) {
return enif_raise_exception(env, atom_mem_error);
}
// TODO: parameterize
int blockSize100k = 9;
int defaultWorkFactor = 0;
int result = BZ2_bzCompressInit(state->stream, blockSize100k, verbosity, defaultWorkFactor);
if (result != BZ_OK) {
destroy_stream_wrapper(env, &state);
return enif_raise_exception(env, convert_error_to_atom(result));
}
return enif_make_resource(env, state);
}
static ERL_NIF_TERM
compress_block(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
bz_stream_wrapper* state;
if (!enif_get_resource(env, argv[0], erl_bz_stream_wrapper, (void**)&state)) {
return enif_make_badarg(env);
}
bz_stream* stream = state->stream;
if (state->input_buffer == NULL) {
// Copy NIF binary input over to the bz_stream and continuation state.
ErlNifBinary input_bin;
if (!enif_inspect_iolist_as_binary(env, argv[1], &input_bin)) {
return enif_make_badarg(env);
}
state->input_buffer = enif_alloc(input_bin.size);
if (state->input_buffer == NULL) {
return enif_raise_exception(env, atom_mem_error);
}
stream->avail_in = input_bin.size;
stream->next_in = state->input_buffer;
memcpy(stream->next_in, input_bin.data, input_bin.size);
} else {
// Continuing to read from the input (last run was limited by the output
// buffer size)
}
// Rewind output cursor for every run, we've already copied the previous run's
// data back to the beam.
stream->avail_out = default_output_buffer_size;
stream->next_out = state->output_buffer;
// FIXME: input "" is magic to put into finishing mode.
int action = stream->avail_in ? BZ_RUN : BZ_FINISH;
debug_print("compressing: %u bytes with action %d\n", stream->avail_in, action);
int result = BZ2_bzCompress(stream, action);
debug_print("compressed: out_bytes=%u\n", default_output_buffer_size - stream->avail_out);
ERL_NIF_TERM status;
if (result == BZ_RUN_OK || result == BZ_FINISH_OK) {
// Ran out of input or output, continue working on this cached input
// buffer but switch to finishing mode.
status = atom_cont;
} else if (result == BZ_STREAM_END) {
// Finished with finishing.
status = atom_stream_end;
} else {
return enif_raise_exception(env, convert_error_to_atom(result));
}
size_t bytes_written = default_output_buffer_size - stream->avail_out;
ERL_NIF_TERM output_term;
unsigned char* output_bin = enif_make_new_binary(env, bytes_written, &output_term);
memcpy(output_bin, state->output_buffer, bytes_written);
return enif_make_tuple3(env, atom_ok, status, output_term);
}
static ERL_NIF_TERM
compress_end(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
bz_stream_wrapper* state;
if (!enif_get_resource(env, argv[0], erl_bz_stream_wrapper, (void**)&state)) {
return enif_make_badarg(env);
}
int result = BZ2_bzCompressEnd(state->stream);
if (result != BZ_OK) {
return enif_raise_exception(env, convert_error_to_atom(result));
}
return atom_ok;
}
static ERL_NIF_TERM
decompress_init(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
bz_stream_wrapper* state = allocate_stream_wrapper(env);
if (state == NULL) {
return enif_raise_exception(env, atom_mem_error);
}
int small_memory = 0;
int result = BZ2_bzDecompressInit(state->stream, verbosity, small_memory);
if (result != BZ_OK) {
destroy_stream_wrapper(env, &state);
return enif_raise_exception(env, convert_error_to_atom(result));
}
return enif_make_resource(env, state);
}
static ERL_NIF_TERM
decompress_block(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
bz_stream_wrapper* state;
if (!enif_get_resource(env, argv[0], erl_bz_stream_wrapper, (void**)&state)) {
return enif_make_badarg(env);
}
bz_stream* stream = state->stream;
// Clean up from the previous block.
if (stream->avail_in == 0 && state->input_buffer != NULL) {
enif_free(state->input_buffer);
state->input_buffer = NULL;
}
// Starting with new input.
if (state->input_buffer == NULL) {
// Copy NIF binary input over to the bz_stream and continuation state.
ErlNifBinary input_bin;
if (!enif_inspect_iolist_as_binary(env, argv[1], &input_bin)) {
return enif_make_badarg(env);
}
state->input_buffer = enif_alloc(input_bin.size);
if (state->input_buffer == NULL) {
return enif_raise_exception(env, atom_mem_error);
}
stream->avail_in = input_bin.size;
stream->next_in = state->input_buffer;
memcpy(stream->next_in, input_bin.data, input_bin.size);
} else {
// Continuing to read from the input (last run was limited by the output
// buffer size)
}
stream->avail_out = default_output_buffer_size;
stream->next_out = state->output_buffer;
int result = BZ2_bzDecompress(stream);
ERL_NIF_TERM status;
if (result == BZ_OK) {
if (stream->avail_in == 0) {
// Ran out of input, continue with the next block.
status = atom_next_input;
} else if (stream->avail_out == 0) {
// Ran out of output buffer space, return and continue with the cached
// input.
status = atom_cont;
} else {
// Should be unreachable.
return enif_raise_exception(env, atom_unknown_error);
}
} else if (result == BZ_STREAM_END) {
status = enif_make_tuple2(env,
atom_stream_end,
enif_make_int(env, stream->avail_in));
} else if (result == BZ_DATA_ERROR_MAGIC) {
debug_print("Ignoring trailing garbage after EOF.\n");
status = atom_bad_magic;
} else {
return enif_raise_exception(env, convert_error_to_atom(result));
}
size_t bytes_written = default_output_buffer_size - stream->avail_out;
ERL_NIF_TERM output_term;
unsigned char* output_bin = enif_make_new_binary(env, bytes_written, &output_term);
memcpy(output_bin, state->output_buffer, bytes_written);
return enif_make_tuple3(env, atom_ok, status, output_term);
}
static ERL_NIF_TERM
decompress_end(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
bz_stream_wrapper* state;
if (!enif_get_resource(env, argv[0], erl_bz_stream_wrapper, (void**)&state)) {
return enif_make_badarg(env);
}
int result = BZ2_bzDecompressEnd(state->stream);
if (result != BZ_OK) {
return enif_raise_exception(env, convert_error_to_atom(result));
}
return atom_ok;
}
static int
load(ErlNifEnv *env, void **priv, ERL_NIF_TERM info) {
erl_bz_stream_wrapper = enif_open_resource_type(env, NULL, "bzip2_stream_wrapper",
&destroy_stream_wrapper, ERL_NIF_RT_CREATE, NULL);
if (erl_bz_stream_wrapper == NULL) {
return -1;
}
atom_ok = enif_make_atom(env, "ok");
atom_cont = enif_make_atom(env, "cont");
atom_next_input = enif_make_atom(env, "next_input");
atom_stream_end = enif_make_atom(env, "stream_end");
atom_bad_magic = enif_make_atom(env, "bad_magic");
atom_sequence_error = enif_make_atom(env, "bz2_sequence_error");
atom_param_error = enif_make_atom(env, "bz2_param_error");
atom_mem_error = enif_make_atom(env, "bz2_mem_error");
atom_data_error = enif_make_atom(env, "bz2_data_error");
atom_io_error = enif_make_atom(env, "bz2_io_error");
atom_unexpected_eof = enif_make_atom(env, "bz2_unexpected_eof");
atom_outbuff_full = enif_make_atom(env, "bz2_outbuff_full");
atom_config_error = enif_make_atom(env, "bz2_config_error");
atom_unknown_error = enif_make_atom(env, "bz2_unknown_error");
return 0;
}
static ErlNifFunc funcs[] = {
{"compress_init!", 0, &compress_init, 0},
{"compress_block!", 2, &compress_block, 0},
{"compress_end!", 1, &compress_end, 0},
{"decompress_init!", 0, &decompress_init, 0},
{"decompress_block!", 2, &decompress_block, 0},
{"decompress_end!", 1, &decompress_end, 0}
};
ERL_NIF_INIT(Elixir.Bzip2.Driver, funcs, &load, NULL, NULL, NULL)