Current section

Files

Jump to
eiconv c_src eiconv_nif.c
Raw

c_src/eiconv_nif.c

/*
* Copyright 2011, 2012, 2013 Maas-Maarten Zeeman
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <iconv.h>
#include <string.h>
#include <errno.h>
#include <erl_nif.h>
static ErlNifResourceType *eiconv_cd_type = NULL;
typedef struct {
iconv_t cd;
ErlNifBinary restbuf;
} eiconv_cd;
static ERL_NIF_TERM
make_atom(ErlNifEnv *env, const char *atom_name)
{
ERL_NIF_TERM atom;
if(enif_make_existing_atom(env, atom_name, &atom, ERL_NIF_LATIN1))
return atom;
return enif_make_atom(env, atom_name);
}
static ERL_NIF_TERM
make_error_tuple(ErlNifEnv *env, const char *reason)
{
return enif_make_tuple2(env, make_atom(env, "error"),
make_atom(env, reason));
}
static void descruct_eiconv_cd(ErlNifEnv *env, void *cd)
{
eiconv_cd *e_cd = (eiconv_cd *) cd;
if(!e_cd)
return;
/* If iconv_open failed, then cd == -1 and we don't have to close. */
if (e_cd->cd != (iconv_t)(-1))
iconv_close(e_cd->cd);
if (e_cd->restbuf.size > 0)
enif_release_binary(&(e_cd->restbuf));
}
static ERL_NIF_TERM
eiconv_make_error(ErlNifEnv* env, int error_number) {
if(error_number == EILSEQ)
return make_error_tuple(env, "eilseq");
if(error_number == EINVAL)
return make_error_tuple(env, "einval");
return make_error_tuple(env, "eunknown");
}
static ERL_NIF_TERM
eiconv_open_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
ErlNifBinary tocode, fromcode;
eiconv_cd *cd;
ERL_NIF_TERM conv_d;
ERL_NIF_TERM eos = enif_make_int(env, 0);
if (!enif_inspect_iolist_as_binary(env,
enif_make_list2(env, argv[0], eos), &tocode))
return enif_make_badarg(env);
if (!enif_inspect_iolist_as_binary(env,
enif_make_list2(env, argv[1], eos), &fromcode))
return enif_make_badarg(env);
cd = enif_alloc_resource(eiconv_cd_type, sizeof(eiconv_cd));
cd->restbuf.data = NULL;
cd->restbuf.size = 0;
cd->cd = iconv_open((const char *) tocode.data, (const char *) fromcode.data);
if((iconv_t)(-1) == cd->cd) {
enif_release_resource(cd);
return eiconv_make_error(env, EINVAL);
}
conv_d = enif_make_resource(env, cd);
enif_release_resource(cd);
return enif_make_tuple2(env, make_atom(env, "ok"), conv_d);
}
static ERL_NIF_TERM
eiconv_chunk_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
eiconv_cd *cd;
ErlNifBinary inbuf, outbuf;
unsigned char *in, *out;
size_t inbytesleft, outbytesleft, outbufsize, rc;
ERL_NIF_TERM list;
if (!enif_get_resource(env, argv[0], eiconv_cd_type, (void **) &cd))
return enif_make_badarg(env);
if (cd->cd == (iconv_t)(-1))
return enif_make_badarg(env);
if (cd->restbuf.size > 0) {
list = enif_make_list2(env,
enif_make_binary(env, &(cd->restbuf)), argv[1]);
if (!enif_inspect_iolist_as_binary(env, list, &inbuf))
return enif_make_badarg(env);
cd->restbuf.data = NULL;
cd->restbuf.size = 0;
} else {
if (!enif_inspect_iolist_as_binary(env, argv[1], &inbuf))
return enif_make_badarg(env);
}
in = inbuf.data;
inbytesleft = inbuf.size;
outbufsize = inbytesleft + (inbytesleft/2);
outbytesleft = outbufsize;
if(!enif_alloc_binary(outbufsize, &outbuf))
return make_error_tuple(env, "enomem");
out = outbuf.data;
do {
rc = iconv(cd->cd, (char **) &in, &inbytesleft, (char **) &out, &outbytesleft);
if(rc == 0)
break;
if (errno == E2BIG) {
/* The result doesn't fit in outbuf, realloc and try again */
outbytesleft += outbufsize;
outbufsize *= 2;
if (!enif_realloc_binary(&outbuf, outbufsize)) {
enif_release_binary(&outbuf);
return make_error_tuple(env, "enomem");
}
out = outbuf.data + (outbufsize - outbytesleft);
} else if (errno == EINVAL) {
/* we got an incomplete character sequence */
if(outbytesleft > 0)
enif_realloc_binary(&outbuf, outbufsize - outbytesleft);
if(!enif_alloc_binary(inbytesleft, &(cd->restbuf))) {
enif_release_binary(&outbuf);
return make_error_tuple(env, "enomem");
}
/* Store the leftover bytes, and use it in the next call */
memcpy(cd->restbuf.data, in, inbytesleft);
cd->restbuf.size = inbytesleft;
return enif_make_tuple2(env, make_atom(env, "more"),
enif_make_binary(env, &outbuf));
} else {
enif_release_binary(&outbuf);
return eiconv_make_error(env, errno);
}
} while (rc != 0);
if(outbytesleft > 0)
enif_realloc_binary(&outbuf, outbufsize - outbytesleft);
return enif_make_tuple2(env, make_atom(env, "done"),
enif_make_binary(env, &outbuf));
}
/*
* Set the cd structure to its initial state.
*/
static ERL_NIF_TERM
eiconv_finalize_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
eiconv_cd *cd;
ERL_NIF_TERM r;
if (!enif_get_resource(env, argv[0], eiconv_cd_type, (void **) &cd))
return enif_make_badarg(env);
if (((eiconv_cd *) cd)->cd == (iconv_t)(-1))
return enif_make_badarg(env);
/* Reset the cd structure */
iconv(cd->cd, NULL, NULL, NULL, 0);
if(cd->restbuf.size > 0) {
r = enif_make_tuple2(env,
make_atom(env, "rest"),
enif_make_binary(env, &(cd->restbuf)));
cd->restbuf.data = NULL;
cd->restbuf.size = 0;
return r;
}
return make_atom(env, "ok");
}
/*
* loading
*/
static int
on_load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info)
{
ErlNifResourceType *rt = enif_open_resource_type(
env, "eiconv", "eiconv_cd_type",
descruct_eiconv_cd, ERL_NIF_RT_CREATE, NULL);
if(!rt)
return -1;
eiconv_cd_type = rt;
return 0;
}
static ErlNifFunc nif_funcs[] = {
{"open", 2, eiconv_open_nif},
{"chunk", 2, eiconv_chunk_nif},
{"finalize", 1, eiconv_finalize_nif}
};
ERL_NIF_INIT(eiconv, nif_funcs, on_load, NULL, NULL, NULL);