Musl uses hard-coded logic for iconv. This patch moves the logic out of C code, and implement a virtual machine for dynamically loading charsets at runtime and do conversion.
434 lines
11 KiB
C
434 lines
11 KiB
C
#include <iconv.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include "locale_impl.h"
|
|
|
|
/* input/output instructions: inputN <receiving/sending_reg> */
|
|
#define VMOP_INPUT8 1
|
|
#define VMOP_INPUT16 2
|
|
#define VMOP_INPUT32 3
|
|
#define VMOP_INPUT64 4
|
|
#define VMOP_OUTPUT8 5
|
|
#define VMOP_OUTPUT16 6
|
|
#define VMOP_OUTPUT32 7
|
|
#define VMOP_OUTPUT64 8
|
|
|
|
/* x-y=z instructions: op <x> <y> <z> */
|
|
#define VMOP_XOR 9
|
|
#define VMOP_AND 10
|
|
#define VMOP_OR 11
|
|
#define VMOP_ADD 13
|
|
#define VMOP_SUB 14
|
|
#define VMOP_SMUL 15
|
|
#define VMOP_UMUL 17
|
|
#define VMOP_EQ 19
|
|
#define VMOP_MT 20
|
|
#define VMOP_LT 21
|
|
#define VMOP_SHL 29
|
|
#define VMOP_SHR 30
|
|
|
|
/* not instruction: not <x> <result> */
|
|
#define VMOP_NOT 12
|
|
|
|
/* jnz instruction: jnz <condition_reg>, <addr(4, be)> */
|
|
#define VMOP_JNZ 22
|
|
|
|
/* jmp instruction: jmp <addr(4, be)> */
|
|
#define VMOP_JMP 23
|
|
|
|
/* load instructions: loadN <addr(4, be)> <receiving_reg> */
|
|
#define VMOP_LOAD8 24
|
|
#define VMOP_LOAD16 25
|
|
#define VMOP_LOAD32 26
|
|
#define VMOP_LOAD64 27
|
|
|
|
/* ptrload instructions: loadN <ptr_reg> <receiving_reg> */
|
|
#define VMOP_PTRLOAD8 33
|
|
#define VMOP_PTRLOAD16 34
|
|
#define VMOP_PTRLOAD32 35
|
|
#define VMOP_PTRLOAD64 36
|
|
|
|
/* store instruction: store64 <sending_reg> <addr(4, be)> */
|
|
#define VMOP_STORE64 28
|
|
|
|
/* return instruction: ret <status_reg> */
|
|
#define VMOP_RET 31
|
|
|
|
#define ICONV_CURRENT_VERSION 1
|
|
#define ICONV_REG_COUNT 16
|
|
#define ICONV_INSC_LEN 8
|
|
|
|
#define ICONV_ERR_EMPTY_STREAM -2
|
|
#define ICONV_ERR_STREAM_OVERFLOW -3
|
|
#define ICONV_ERR_ILLEGAL_OPCODE -4
|
|
#define ICONV_ERR_MEMORY_OVERFLOW -5
|
|
|
|
#define ICONV_CHARSET_PATH_PREFIX "/etc/iconv/"
|
|
|
|
static const uint8_t vm_binary_magic[4] = { 'S', 'I', 'C', 'V' };
|
|
|
|
struct vm_binary_header {
|
|
uint8_t magic[4];
|
|
uint32_t version;
|
|
uint32_t from_utf8_addr;
|
|
uint32_t to_utf8_addr;
|
|
};
|
|
|
|
struct iconv_vm {
|
|
uint8_t *memory;
|
|
size_t memlen;
|
|
size_t pc;
|
|
uint64_t reg[ICONV_REG_COUNT];
|
|
};
|
|
|
|
struct iconv_stream {
|
|
char *buf;
|
|
size_t off;
|
|
size_t len;
|
|
};
|
|
|
|
static struct iconv_vm iconv_vm_new(uint8_t *memory, size_t memlen) {
|
|
struct iconv_vm vm;
|
|
vm.memory = memory;
|
|
vm.memlen = memlen;
|
|
vm.pc = 0;
|
|
memset(&vm.reg, 0, sizeof(uint64_t) * ICONV_REG_COUNT);
|
|
return vm;
|
|
}
|
|
|
|
static uint64_t htonll(uint64_t val) {
|
|
return (((uint64_t) htonl(val)) << 32) + htonl(val >> 32);
|
|
}
|
|
|
|
static uint64_t ntohll(uint64_t val) {
|
|
return (((uint64_t) ntohl(val)) << 32) + ntohl(val >> 32);
|
|
}
|
|
|
|
static uint64_t vm_reg_read_raw(struct iconv_vm *vm, uint8_t n) {
|
|
if(n >= ICONV_REG_COUNT) {
|
|
return 0;
|
|
}
|
|
return vm->reg[n];
|
|
}
|
|
|
|
static void vm_reg_write_raw(struct iconv_vm *vm, uint8_t n, uint64_t val) {
|
|
if(n >= ICONV_REG_COUNT) {
|
|
return;
|
|
}
|
|
vm->reg[n] = val;
|
|
}
|
|
|
|
static uint64_t vm_reg_read(struct iconv_vm *vm, uint8_t n) {
|
|
return ntohll(vm_reg_read_raw(vm, n));
|
|
}
|
|
|
|
static void vm_reg_write(struct iconv_vm *vm, uint8_t n, uint64_t val) {
|
|
vm_reg_write_raw(vm, n, htonll(val));
|
|
}
|
|
|
|
static int iconv_stream_read(struct iconv_stream *st, void *dst, size_t n) {
|
|
if(st->off + n > st->len) {
|
|
return -1;
|
|
}
|
|
memcpy(dst, st->buf + st->off, n);
|
|
st->off += n;
|
|
return 0;
|
|
}
|
|
|
|
static int iconv_stream_write(struct iconv_stream *st, const void *src, size_t n) {
|
|
if(st->off + n > st->len) {
|
|
return -1;
|
|
}
|
|
memcpy(st->buf + st->off, src, n);
|
|
st->off += n;
|
|
return 0;
|
|
}
|
|
|
|
static int vm_memory_read(struct iconv_vm *vm, size_t off, void *buf, size_t n) {
|
|
if(off + n > vm->memlen) {
|
|
return -1;
|
|
}
|
|
memcpy(buf, vm->memory + off, n);
|
|
return 0;
|
|
}
|
|
|
|
static int vm_memory_write(struct iconv_vm *vm, size_t off, const void *buf, size_t n) {
|
|
if(off + n > vm->memlen) {
|
|
return -1;
|
|
}
|
|
memcpy(vm->memory + off, buf, n);
|
|
return 0;
|
|
}
|
|
|
|
static int vm_execute(struct iconv_vm *vm, struct iconv_stream *in, struct iconv_stream *out) {
|
|
uint8_t insc[ICONV_INSC_LEN];
|
|
size_t mop_nbytes;
|
|
char mop_buf[8];
|
|
int64_t signed_calc_result;
|
|
char addr_buf[4];
|
|
while(1) {
|
|
memset(&mop_buf, 0, 8);
|
|
if(vm_memory_read(vm, vm->pc, &insc, ICONV_INSC_LEN) == -1) {
|
|
return ICONV_ERR_MEMORY_OVERFLOW;
|
|
}
|
|
vm->pc += ICONV_INSC_LEN;
|
|
switch(insc[0]) {
|
|
case VMOP_INPUT8:
|
|
mop_nbytes = 1;
|
|
goto vmop_input;
|
|
case VMOP_INPUT16:
|
|
mop_nbytes = 2;
|
|
goto vmop_input;
|
|
case VMOP_INPUT32:
|
|
mop_nbytes = 4;
|
|
goto vmop_input;
|
|
case VMOP_INPUT64:
|
|
mop_nbytes = 8;
|
|
vmop_input:
|
|
if(iconv_stream_read(in, (mop_buf + 8 - mop_nbytes), mop_nbytes) == -1) {
|
|
return ICONV_ERR_EMPTY_STREAM;
|
|
}
|
|
vm_reg_write_raw(vm, insc[1], *(uint64_t*)&mop_buf);
|
|
break;
|
|
case VMOP_OUTPUT8:
|
|
mop_nbytes = 1;
|
|
goto vmop_output;
|
|
case VMOP_OUTPUT16:
|
|
mop_nbytes = 2;
|
|
goto vmop_output;
|
|
case VMOP_OUTPUT32:
|
|
mop_nbytes = 4;
|
|
goto vmop_output;
|
|
case VMOP_OUTPUT64:
|
|
mop_nbytes = 8;
|
|
vmop_output:
|
|
*(uint64_t*)&mop_buf = vm_reg_read_raw(vm, insc[1]);
|
|
if(iconv_stream_write(out, (mop_buf + 8 - mop_nbytes), mop_nbytes) == -1) {
|
|
return ICONV_ERR_STREAM_OVERFLOW;
|
|
}
|
|
break;
|
|
case VMOP_XOR:
|
|
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) ^ vm_reg_read(vm, insc[2]));
|
|
break;
|
|
case VMOP_AND:
|
|
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) & vm_reg_read(vm, insc[2]));
|
|
break;
|
|
case VMOP_OR:
|
|
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) | vm_reg_read(vm, insc[2]));
|
|
break;
|
|
case VMOP_ADD:
|
|
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) + vm_reg_read(vm, insc[2]));
|
|
break;
|
|
case VMOP_SUB:
|
|
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) - vm_reg_read(vm, insc[2]));
|
|
break;
|
|
case VMOP_SMUL:
|
|
signed_calc_result = (int64_t)vm_reg_read(vm, insc[1]) * (int64_t)vm_reg_read(vm, insc[2]);
|
|
vm_reg_write(vm, insc[3], signed_calc_result);
|
|
break;
|
|
case VMOP_UMUL:
|
|
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) * vm_reg_read(vm, insc[2]));
|
|
break;
|
|
case VMOP_EQ:
|
|
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) == vm_reg_read(vm, insc[2]));
|
|
break;
|
|
case VMOP_LT:
|
|
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) < vm_reg_read(vm, insc[2]));
|
|
break;
|
|
case VMOP_MT:
|
|
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) > vm_reg_read(vm, insc[2]));
|
|
break;
|
|
case VMOP_SHL:
|
|
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) << vm_reg_read(vm, insc[2]));
|
|
break;
|
|
case VMOP_SHR:
|
|
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) >> vm_reg_read(vm, insc[2]));
|
|
break;
|
|
case VMOP_NOT:
|
|
vm_reg_write(vm, insc[2], ~vm_reg_read(vm, insc[1]));
|
|
break;
|
|
case VMOP_JNZ:
|
|
if(vm_reg_read(vm, insc[1])) {
|
|
memcpy(&addr_buf, insc + 2, 4);
|
|
goto vmop_jop;
|
|
}
|
|
break;
|
|
case VMOP_JMP:
|
|
memcpy(&addr_buf, insc + 1, 4);
|
|
vmop_jop:
|
|
vm->pc = ntohl(*(uint32_t*)&addr_buf);
|
|
break;
|
|
case VMOP_LOAD8:
|
|
mop_nbytes = 1;
|
|
goto vmop_load;
|
|
case VMOP_LOAD16:
|
|
mop_nbytes = 2;
|
|
goto vmop_load;
|
|
case VMOP_LOAD32:
|
|
mop_nbytes = 4;
|
|
goto vmop_load;
|
|
case VMOP_LOAD64:
|
|
mop_nbytes = 8;
|
|
vmop_load:
|
|
memcpy(&addr_buf, insc + 1, 4);
|
|
if(vm_memory_read(vm, ntohl(*(uint32_t*)&addr_buf), (mop_buf + 8 - mop_nbytes), mop_nbytes) == -1) {
|
|
return ICONV_ERR_MEMORY_OVERFLOW;
|
|
}
|
|
vm_reg_write_raw(vm, insc[5], *(uint64_t*)&mop_buf);
|
|
break;
|
|
case VMOP_PTRLOAD8:
|
|
mop_nbytes = 1;
|
|
goto vmop_ptr_load;
|
|
case VMOP_PTRLOAD16:
|
|
mop_nbytes = 2;
|
|
goto vmop_ptr_load;
|
|
case VMOP_PTRLOAD32:
|
|
mop_nbytes = 4;
|
|
goto vmop_ptr_load;
|
|
case VMOP_PTRLOAD64:
|
|
mop_nbytes = 8;
|
|
vmop_ptr_load:
|
|
if(vm_memory_read(vm, vm_reg_read(vm, insc[1]), (mop_buf + 8 - mop_nbytes), mop_nbytes) == -1) {
|
|
return ICONV_ERR_MEMORY_OVERFLOW;
|
|
}
|
|
vm_reg_write_raw(vm, insc[2], *(uint64_t*)&mop_buf);
|
|
break;
|
|
case VMOP_STORE64:
|
|
memcpy(&addr_buf, insc + 2, 4);
|
|
*(uint64_t*)&mop_buf = vm_reg_read_raw(vm, insc[1]);
|
|
if(vm_memory_write(vm, ntohl(*(uint32_t*)&addr_buf), &mop_buf, 8) == -1) {
|
|
return ICONV_ERR_MEMORY_OVERFLOW;
|
|
}
|
|
break;
|
|
case VMOP_RET:
|
|
return (int)vm_reg_read(vm, insc[1]);
|
|
default:
|
|
return ICONV_ERR_ILLEGAL_OPCODE;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct iconv {
|
|
struct iconv_vm from;
|
|
struct iconv_vm to;
|
|
};
|
|
|
|
static char *read_file(const char *path, size_t *size) {
|
|
struct stat st;
|
|
int fd = open(path, O_RDONLY);
|
|
if (fd == -1) {
|
|
return NULL;
|
|
}
|
|
if (fstat(fd, &st) == -1) {
|
|
return NULL;
|
|
}
|
|
if (st.st_size > 128 * 1024 || st.st_size <= sizeof(struct vm_binary_header)) {
|
|
return NULL;
|
|
}
|
|
char *buf = malloc(st.st_size);
|
|
memset(buf, 0, st.st_size);
|
|
while (read(fd, buf, st.st_size) > 0) {}
|
|
*size = st.st_size;
|
|
return buf;
|
|
}
|
|
|
|
iconv_t iconv_open(const char *to, const char *from) {
|
|
size_t path_prefix_len = strlen(ICONV_CHARSET_PATH_PREFIX);
|
|
char *from_memory = NULL;
|
|
char *to_memory = NULL;
|
|
char *from_path = calloc(path_prefix_len + strlen(from) + 1, 1);
|
|
char *to_path = calloc(path_prefix_len + strlen(to) + 1, 1);
|
|
size_t from_size = 0;
|
|
size_t to_size = 0;
|
|
if (!from_path || !to_path) {
|
|
goto error;
|
|
}
|
|
strcpy(from_path, ICONV_CHARSET_PATH_PREFIX);
|
|
strcpy(to_path, ICONV_CHARSET_PATH_PREFIX);
|
|
strcpy(from_path + path_prefix_len, from);
|
|
strcpy(to_path + path_prefix_len, to);
|
|
|
|
from_memory = read_file(from_path, &from_size);
|
|
to_memory = read_file(to_path, &to_size);
|
|
free(from_path);
|
|
free(to_path);
|
|
from_path = NULL;
|
|
to_path = NULL;
|
|
if (!from_memory || !to_memory) {
|
|
goto error;
|
|
}
|
|
|
|
struct iconv *result = malloc(sizeof(struct iconv));
|
|
result->from = iconv_vm_new((uint8_t*)from_memory, from_size);
|
|
result->to = iconv_vm_new((uint8_t*)to_memory, to_size);
|
|
|
|
struct vm_binary_header *from_header = (struct vm_binary_header*)from_memory;
|
|
struct vm_binary_header *to_header = (struct vm_binary_header*)to_memory;
|
|
if(memcmp(&from_header->magic, &vm_binary_magic, sizeof(vm_binary_magic))
|
|
|| ntohl(from_header->version) != ICONV_CURRENT_VERSION
|
|
|| memcmp(&to_header->magic, &vm_binary_magic, sizeof(vm_binary_magic))
|
|
|| ntohl(to_header->version) != ICONV_CURRENT_VERSION) {
|
|
goto error;
|
|
}
|
|
|
|
return result;
|
|
|
|
error:
|
|
free(from_path);
|
|
free(to_path);
|
|
free(from_memory);
|
|
free(to_memory);
|
|
errno = EINVAL;
|
|
return (iconv_t)-1;
|
|
}
|
|
|
|
size_t iconv(iconv_t cd, char **restrict in, size_t *restrict inb, char **restrict out, size_t *restrict outb) {
|
|
struct iconv *iconv = cd;
|
|
char utf8_buf[6];
|
|
struct iconv_stream utf8_stream;
|
|
struct iconv_stream in_stream;
|
|
struct iconv_stream out_stream;
|
|
struct vm_binary_header *from_header = (struct vm_binary_header*)iconv->from.memory;
|
|
struct vm_binary_header *to_header = (struct vm_binary_header*)iconv->to.memory;
|
|
utf8_stream.buf = utf8_buf;
|
|
utf8_stream.off = 0;
|
|
utf8_stream.len = 6;
|
|
in_stream.buf = *in;
|
|
in_stream.off = 0;
|
|
in_stream.len = *inb;
|
|
out_stream.buf = *out;
|
|
out_stream.off = 0;
|
|
out_stream.len = *outb;
|
|
int status;
|
|
|
|
for(;;) {
|
|
iconv->from.pc = ntohl(from_header->to_utf8_addr);
|
|
iconv->to.pc = ntohl(to_header->from_utf8_addr);
|
|
status = vm_execute(&iconv->from, &in_stream, &utf8_stream);
|
|
if(status < 0) break;
|
|
utf8_stream.off = 0;
|
|
status = vm_execute(&iconv->to, &utf8_stream, &out_stream);
|
|
if(status < 0) break;
|
|
utf8_stream.off = 0;
|
|
}
|
|
*in += in_stream.off;
|
|
*out += out_stream.off;
|
|
*inb -= in_stream.off;
|
|
*outb -= out_stream.off;
|
|
return in_stream.off;
|
|
}
|
|
|
|
int iconv_close(iconv_t cd) {
|
|
struct iconv *iconv = cd;
|
|
free(iconv->from.memory);
|
|
free(iconv->to.memory);
|
|
free(iconv);
|
|
return 0;
|
|
}
|