Current section
Files
Jump to
Current section
Files
c_src/exmagic.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <magic.h>
#include "erl_nif.h"
/**
* Given an ERL_NIF_TERM, will attempt to convert it to binary and then to a
* NULL-terminated C string.
*
* On success, it will return the allocated C string. On failure, the `ret`
* parameter will be set to an Erlang term that indicates the failure
* condition, and the function will return NULL.
*
* The return value should be freed using the free() function.
*/
static char*
binary_to_cstr(ErlNifEnv *env, ERL_NIF_TERM term, ERL_NIF_TERM *ret) {
ErlNifBinary buf;
char *cstr = NULL;
if (!enif_inspect_binary(env, term, &buf)) {
*ret = enif_make_badarg(env);
return NULL;
}
cstr = calloc(buf.size + 1, 1);
if (NULL == cstr) {
*ret = enif_make_tuple2(
env,
enif_make_atom(env, "error"),
enif_make_atom(env, "fail_to_allocate_memory")
);
return NULL;
}
/* NOTE: don't need to null-terminate, since calloc() does that. */
memcpy(cstr, buf.data, buf.size);
/* All good! */
return cstr;
}
/**
* Given a NULL-terminated C string, will convert it to an Erlang binary.
*
* Will return a boolean indicating success. On failure, the `ret` parameter
* will be set to an Erlang term that indicates the failure condition, and the
* function will return NULL.
*/
static bool
cstr_to_binary(ErlNifEnv *env, const char *cstr, ErlNifBinary *buf, ERL_NIF_TERM *ret) {
size_t slen;
slen = strlen(cstr);
if (!enif_alloc_binary(slen, buf)) {
*ret = enif_make_tuple2(
env,
enif_make_atom(env, "error"),
enif_make_atom(env, "fail_to_build_binary")
);
return false;
}
memcpy(buf->data, cstr, slen);
return true;
}
/**
* Creates a libmagic cookie and opens the given database.
*
* On success, will return the magic cookie. On failure, the `ret` parameter
* will be set to an Erlang term that indicates the failure condition, and the
* function will return NULL.
*
* The returned cookie should be freed with `magic_close`.
*/
static magic_t
open_magic_database(ErlNifEnv *env, int flags, char *dbpath, ERL_NIF_TERM *ret) {
magic_t cookie;
/* Create a libmagic handle */
cookie = magic_open(flags);
if (NULL == cookie) {
*ret = enif_make_tuple2(
env,
enif_make_atom(env, "error"),
enif_make_atom(env, "fail_to_open_magic")
);
}
/* Load the magic database at the given path. */
if (magic_load(cookie, dbpath) != 0) {
*ret = enif_make_tuple2(
env,
enif_make_atom(env, "error"),
enif_make_atom(env, "fail_to_load_magic_database")
);
magic_close(cookie);
return NULL;
}
/* All good */
return cookie;
}
/**
* Returns the magic information from the given buffer.
*/
static ERL_NIF_TERM
from_buffer(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
ERL_NIF_TERM ret;
ErlNifBinary buf, magic_bin;
magic_t magic_cookie = NULL;
char *dbstr = NULL;
const char *magic_output = NULL;
if (argc != 2) {
ret = enif_make_badarg(env);
goto cleanup;
}
if (!enif_inspect_binary(env, argv[0], &buf)) {
ret = enif_make_badarg(env);
goto cleanup;
}
/* Copy the magic DB path to a null-terminated C string. */
dbstr = binary_to_cstr(env, argv[1], &ret);
if (NULL == dbstr) {
/* ret set by above function */
goto cleanup;
}
/* Create a libmagic handle */
magic_cookie = open_magic_database(env, MAGIC_MIME_TYPE, dbstr, &ret);
if (NULL == magic_cookie) {
/* ret set by above function */
goto cleanup;
}
/* Get the magic data from libmagic. */
magic_output = magic_buffer(magic_cookie, buf.data, buf.size);
if (NULL == magic_output) {
ret = enif_make_tuple2(
env,
enif_make_atom(env, "error"),
enif_make_atom(env, "fail_to_magic")
);
goto cleanup;
}
/* Create a return binary that contains the magic data. */
if (!cstr_to_binary(env, magic_output, &magic_bin, &ret)) {
/* ret set by above function */
goto cleanup;
}
/* If we get here, everything is good! Return it :-) */
ret = enif_make_tuple2(
env,
enif_make_atom(env, "ok"),
enif_make_binary(env, &magic_bin)
);
cleanup:
if (magic_cookie != NULL) magic_close(magic_cookie);
if (dbstr != NULL) free(dbstr);
return ret;
}
/**
* Returns the magic information from the given file.
*/
static ERL_NIF_TERM
from_file(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
ERL_NIF_TERM ret;
ErlNifBinary magic_bin;
magic_t magic_cookie = NULL;
char *filepath = NULL;
char *dbstr = NULL;
const char *magic_output = NULL;
if (argc != 2) {
ret = enif_make_badarg(env);
goto cleanup;
}
/* Copy the inputs to NULL-terminated C strings. */
filepath = binary_to_cstr(env, argv[0], &ret);
if (NULL == filepath) {
/* ret set by above function */
goto cleanup;
}
dbstr = binary_to_cstr(env, argv[1], &ret);
if (NULL == dbstr) {
/* ret set by above function */
goto cleanup;
}
/* Create a libmagic handle */
magic_cookie = open_magic_database(env, MAGIC_MIME_TYPE, dbstr, &ret);
if (NULL == magic_cookie) {
/* ret set by above function */
goto cleanup;
}
/* Get the magic data from libmagic. */
magic_output = magic_file(magic_cookie, filepath);
if (NULL == magic_output) {
ret = enif_make_tuple2(
env,
enif_make_atom(env, "error"),
enif_make_atom(env, "fail_to_magic")
);
goto cleanup;
}
/* Create a return binary that contains the magic data. */
if (!cstr_to_binary(env, magic_output, &magic_bin, &ret)) {
/* ret set by above function */
goto cleanup;
}
/* If we get here, everything is good! Return it :-) */
ret = enif_make_tuple2(
env,
enif_make_atom(env, "ok"),
enif_make_binary(env, &magic_bin)
);
cleanup:
if (magic_cookie != NULL) magic_close(magic_cookie);
if (dbstr != NULL) free(dbstr);
if (filepath != NULL) free(filepath);
return ret;
}
/**
* Returns the version of libmagic in use.
*/
static ERL_NIF_TERM
version(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
ERL_NIF_TERM ret;
if (argc != 0) {
ret = enif_make_badarg(env);
goto cleanup;
}
ret = enif_make_int(
env,
magic_version()
);
cleanup:
return ret;
}
static int upgrade(ErlNifEnv* caller_env, void **priv_data, void **old_priv_data, ERL_NIF_TERM load_info)
{
/* nothing to do */
return 0;
}
/**
* Function definitions for our NIF.
*/
static ErlNifFunc
nif_funcs[] = {
/* {erl_function_name, erl_function_arity, c_function, flags} */
{"nif_from_buffer", 2, from_buffer, 0},
{"nif_from_file", 2, from_file, 0},
{"version", 0, version, 0},
};
/**
* Finally, we call ERL_NIF_INIT, which is a macro, with our Erlang module
* name, the list of function mappings, and 4 pointers to functions: load,
* reload, upgrade, and unload.
*/
ERL_NIF_INIT(Elixir.ExMagic, nif_funcs, NULL, NULL, upgrade, NULL)