Packages

An advanced iconv nif wrapper.

Current section

Files

Jump to
iconverl c_src iconverl.c
Raw

c_src/iconverl.c

/*
* Copyright (c) 2013 Eric des Courtis <eric.des.courtis@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/*
┌─────────────────────────────────────────────────────────────┐
│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│
│▓ ▓│
│▓ ██ ██▀▀██ ██▀██ ███ ██ ██ ██ ██████ ██▀▀██ ██ ▓│
│▓ ██ ██ ▀ ██ ██ ██████ ██ ██ ██▄▄ ██▄▄██ ██ ▓│
│▓ ██ ██ ██ ██ ██ ███ ████ ██▀▀ ██▀██ ██ ▄ ▓│
│▓ ██ ██▄▄██ ██▄██ ██ ██ ██ ██████ ██ ██ ██▄▄██ ▓│
│▓ ▓│
│▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓│
└─────────────────────────────────────────────────────────────┘
*/
#include "erl_nif.h"
#include <iconv.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
#include <stdlib.h>
#define CHUNK_SIZE 4096
static ErlNifResourceType *iconv_cd_type = NULL;
static ERL_NIF_TERM iconv_iconv_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM iconv_open_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM iconv_reset_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM iconv_ignores_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
static ERL_NIF_TERM make_error_tuple_from_errno(ErlNifEnv* env, int errn);
static ERL_NIF_TERM make_error_tuple_from_string(ErlNifEnv* env, const char *error);
static ERL_NIF_TERM make_error_tuple(ErlNifEnv* env, ERL_NIF_TERM error);
static void garbage_collect_iconv_cd(ErlNifEnv *env, void *cd);
static int handle_load(ErlNifEnv *env, void **priv, ERL_NIF_TERM load_info);
static int handle_upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info);
/* ERL_NIF >= 2.8 */
#if ERL_NIF_MAJOR_VERSION > 2 || \
(ERL_NIF_MAJOR_VERSION == 2 && \
(ERL_NIF_MINOR_VERSION > 8 || (ERL_NIF_MINOR_VERSION == 8)))
static ErlNifFunc nif_funcs[] = {
{"open_priv", 2, iconv_open_nif, 0 },
{"iconv", 2, iconv_iconv_nif, 0 },
{"reset", 1, iconv_reset_nif, 0 },
{"ignores", 1, iconv_ignores_nif, 0 }
};
#else
static ErlNifFunc nif_funcs[] = {
{"open_priv", 2, iconv_open_nif },
{"iconv", 2, iconv_iconv_nif },
{"reset", 1, iconv_reset_nif },
{"ignores", 1, iconv_ignores_nif }
};
#endif
typedef struct {
iconv_t conv_desc;
int ignore;
} iconv_state_t;
static ERL_NIF_TERM iconv_open_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
unsigned int to_len;
unsigned int from_len;
char *to;
char *from;
iconv_state_t *state_desc_ref;
iconv_state_t state;
int res;
ERL_NIF_TERM resource;
assert(argc == 2);
if(!enif_get_list_length(env, argv[0], &to_len)){
return make_error_tuple_from_string(env, "to_not_a_list");
}
if(!enif_get_list_length(env, argv[1], &from_len)){
return make_error_tuple_from_string(env, "from_not_a_list");
}
to = (char *)enif_alloc(++to_len);
if(to == NULL){
return make_error_tuple_from_string(env, "to_enif_alloc");
}
from = (char *)enif_alloc(++from_len);
if(from == NULL){
enif_free(to);
return make_error_tuple_from_string(env, "from_enif_alloc");
}
res = enif_get_string(
env,
argv[0],
to,
to_len,
ERL_NIF_LATIN1
);
if(res < 0){
enif_free(to);
enif_free(from);
return make_error_tuple_from_string(env, "to_enif_get_string_truncation");
}
if(res == 0){
enif_free(to);
enif_free(from);
return make_error_tuple_from_string(env, "to_enif_get_string_cannot_encode");
}
res = enif_get_string(
env,
argv[1],
from,
from_len,
ERL_NIF_LATIN1
);
if(res < 0){
enif_free(to);
enif_free(from);
return make_error_tuple_from_string(env, "from_enif_get_string_truncation");
}
if(res == 0){
enif_free(to);
enif_free(from);
return make_error_tuple_from_string(env, "from_enif_get_string_cannot_encode");
}
state.ignore = 0;
if(strlen(to) > 6){
if(!strcasecmp(to + strlen(to) -6, "ignore")){
state.ignore = 1;
}
}
state.conv_desc = iconv_open(to, from);
if(state.conv_desc == (iconv_t) -1){
enif_free(to);
enif_free(from);
switch(errno){
case EINVAL:
return make_error_tuple_from_string(env, "unsupported");
default:
return make_error_tuple_from_errno(env, errno);
}
}
state_desc_ref = (iconv_state_t *)enif_alloc_resource(iconv_cd_type, sizeof(iconv_state_t));
*state_desc_ref = state;
enif_free(to);
enif_free(from);
resource = enif_make_resource(env, state_desc_ref);
enif_release_resource(state_desc_ref);
return resource;
}
static ERL_NIF_TERM iconv_iconv_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
iconv_state_t *cd_ptr = NULL;
int res;
size_t ret;
ErlNifBinary in_bin;
ErlNifBinary out_bin;
ERL_NIF_TERM final_bin;
char *inbuf;
char *outbuf;
size_t inbytesleft;
size_t outbytesleft;
assert(argc == 2);
if(!enif_get_resource(env, argv[0], iconv_cd_type, (void **)&cd_ptr)) {
return make_error_tuple_from_string(env, "bad_resource");
}
assert(cd_ptr != NULL);
res = enif_inspect_binary(env, argv[1], &in_bin);
if(res == 0){
return make_error_tuple_from_string(env, "not_a_byte_aligned_binary");
}
if(enif_alloc_binary(CHUNK_SIZE, &out_bin) == 0){
return make_error_tuple_from_string(env, "enif_alloc_binary");
}
inbytesleft = in_bin.size;
outbytesleft = out_bin.size;
inbuf = (char *)in_bin.data;
outbuf = (char *)out_bin.data;
ret = iconv(cd_ptr->conv_desc, &inbuf, &inbytesleft, &outbuf, &outbytesleft);
if(enif_realloc_binary(&out_bin, out_bin.size - outbytesleft) == 0){
enif_release_binary(&out_bin);
return make_error_tuple_from_string(env, "enif_realloc_binary");
}
final_bin = enif_make_binary(env, &out_bin);
if(ret == (size_t)-1){
switch(errno){
case E2BIG:
return enif_make_tuple4(
env,
enif_make_atom(env, "ok"),
enif_make_atom(env, "e2big"),
enif_make_uint64(env, (ErlNifUInt64)inbytesleft),
final_bin
);
case EILSEQ:
if(cd_ptr->ignore == 1){
if(inbytesleft == 0){
ret = 0;
break;
}else{
if(in_bin.size == inbytesleft){
return enif_make_tuple4(
env,
enif_make_atom(env, "ok"),
enif_make_atom(env, "e2big"),
enif_make_uint64(env, (ErlNifUInt64)inbytesleft - 1),
final_bin
);
}else{
return enif_make_tuple4(
env,
enif_make_atom(env, "ok"),
enif_make_atom(env, "e2big"),
enif_make_uint64(env, (ErlNifUInt64)inbytesleft),
final_bin
);
}
}
}
return enif_make_tuple4(
env,
enif_make_atom(env, "ok"),
enif_make_atom(env, "eilseq"),
enif_make_uint64(env, (ErlNifUInt64)inbytesleft),
final_bin
);
case EINVAL:
if(cd_ptr->ignore == 1){
return enif_make_tuple3(
env,
enif_make_atom(env, "ok"),
enif_make_uint64(env, (ErlNifUInt64)inbytesleft - 1),
final_bin
);
}
return enif_make_tuple4(
env,
enif_make_atom(env, "ok"),
enif_make_atom(env, "einval"),
enif_make_uint64(env, (ErlNifUInt64)inbytesleft),
final_bin
);
case EBADF:
return make_error_tuple_from_string(env, "ebadf");
default:
return make_error_tuple_from_errno(env, errno);
}
}
return enif_make_tuple3(
env,
enif_make_atom(env, "ok"),
enif_make_uint64(env, (ErlNifUInt64)inbytesleft),
final_bin
);
}
static ERL_NIF_TERM iconv_reset_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
iconv_state_t *cd_ptr = NULL;
size_t ret;
assert(argc == 1);
if(!enif_get_resource(env, argv[0], iconv_cd_type, (void **)&cd_ptr)) {
return make_error_tuple_from_string(env, "bad_resource");
}
assert(cd_ptr != NULL);
ret = iconv(cd_ptr->conv_desc, NULL, NULL, NULL, NULL);
if(ret == (size_t) -1) {
return make_error_tuple_from_errno(env, errno);
}
return enif_make_atom(env, "ok");
}
static ERL_NIF_TERM iconv_ignores_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
iconv_state_t *cd_ptr = NULL;
size_t ret;
assert(argc == 1);
if(!enif_get_resource(env, argv[0], iconv_cd_type, (void **)&cd_ptr)) {
return make_error_tuple_from_string(env, "bad_resource");
}
assert(cd_ptr != NULL);
if(cd_ptr->ignore == 1){
return enif_make_atom(env, "true");
}else{
return enif_make_atom(env, "false");
}
}
static ERL_NIF_TERM make_error_tuple_from_errno(ErlNifEnv* env, int errn)
{
return enif_make_tuple2(
env,
enif_make_atom(env, "error"),
enif_make_int(env, errn)
);
}
static ERL_NIF_TERM make_error_tuple_from_string(ErlNifEnv* env, const char *error)
{
return make_error_tuple(env, enif_make_atom(env, error));
}
static ERL_NIF_TERM make_error_tuple(ErlNifEnv* env, ERL_NIF_TERM error)
{
return enif_make_tuple2(env, enif_make_atom(env, "error"), error);
}
static void garbage_collect_iconv_cd(ErlNifEnv *env, void *cd)
{
int res;
iconv_state_t *cd_ptr = (iconv_state_t *)cd;
res = iconv_close(cd_ptr->conv_desc);
if(res == -1){
fprintf(
stderr,
"iconv_close() in file: %s at line: %d failed with errno: %d\n",
__FILE__,
__LINE__,
errno
);
abort();
}
assert(res == 0);
}
static int handle_load(ErlNifEnv *env, void **priv, ERL_NIF_TERM load_info)
{
ErlNifResourceType *rt;
ErlNifResourceFlags rf;
rt = enif_open_resource_type(
env,
NULL,
"iconv_cd_type",
garbage_collect_iconv_cd,
ERL_NIF_RT_CREATE,
&rf
);
if(rt == NULL) return -1;
iconv_cd_type = rt;
return 0;
}
static int handle_upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info)
{
return 0;
}
ERL_NIF_INIT(iconverl, nif_funcs, handle_load, NULL, handle_upgrade, NULL)