/* This file is part of vmod-binlog
Copyright (C) 2013-2014 Sergey Poznyakoff
Vmod-binlog is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
Vmod-binlog is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with vmod-binlog. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "pack.h"
/* Data format specification is a simplified form of Perl pack() template.
It consists of a series of template letters optionally followed by
numeric repeat count (which may be enclosed in square brackets). The
valid template letters are:
Z A null-terminated (ASCIZ) string of at most N-1 characters, will be
null padded. This letter must be followed by repeat count.
c A signed char (8-bit) value.
s A signed short (16-bit) value.
S An unsigned short value.
l A signed long (32-bit) value.
L An unsigned long value.
q A signed quad (64-bit) value.
Q An unsigned quad value.
i A signed integer value.
I A unsigned integer value.
n An unsigned short (16-bit) in "network" (big-endian) order.
N An unsigned long (32-bit) in "network" (big-endian) order.
v An unsigned short (16-bit) in "VAX" (little-endian) order.
V An unsigned long (32-bit) in "VAX" (little-endian) order.
f A single-precision float in native format.
d A double-precision float in native format.
x A null byte (a.k.a ASCII NUL, "\000", chr(0))
X Back up a byte.
@ Null-fill or truncate to absolute position, counted from the
current position.
. Null-fill or truncate to absolute position specified by
the repeat count.
*/
#define F_REP 0x01
struct packspec {
int ch;
int flags;
void (*packer)(struct packenv *, int);
void (*unpacker)(struct packenv *, int);
};
struct packinst {
struct packinst *next;
struct packspec *spec;
int rep;
int cur;
};
static char *
packenv_get(struct packenv *env)
{
if (env->argi == env->argc)
return NULL;
return env->argv[env->argi++];
}
static void
printunum(FILE *fp, uintmax_t num)
{
char numbuf[512];
char *p = numbuf + sizeof(numbuf);
*--p = 0;
do
*--p = num % 10 + '0';
while (num /= 10);
fputs(p, fp);
}
static void
printsnum(FILE *fp, intmax_t num)
{
if (num < 0) {
fputc('-', fp);
num = - num;
}
printunum(fp, num);
}
static int
getunum(char *s, uintmax_t maxval, uintmax_t *retval)
{
uintmax_t x = 0;
for (; *s && isspace(*s); s++);
for (; *s; s++) {
if (!isdigit(*s)) {
errno = EINVAL;
return -1;
}
x = x*10 + *s - '0';
}
if (x > maxval) {
errno = ERANGE;
return -1;
}
*retval = x;
return 0;
}
static int
getsnum(char *s, intmax_t minval, intmax_t maxval, intmax_t *retval)
{
uintmax_t x = 0;
int neg = 0;
for (; *s && isspace(*s); s++);
if (*s == '-') {
neg = 1;
++s;
}
for (; *s; s++) {
if (!isdigit(*s)) {
errno = EINVAL;
return -1;
}
x = x*10 + *s - '0';
}
if (neg) {
if (x > -minval) {
errno = ERANGE;
return -1;
}
*retval = -x;
} else if (x > maxval) {
errno = ERANGE;
return -1;
} else
*retval = x;
return 0;
}
#define GETSARG(env, type, minv, maxv) do { \
if ((env)->buf_base) { \
char *arg = packenv_get(env); \
if (arg) { \
intmax_t x; \
if (getsnum(arg, minv, maxv, &x) == 0) { \
type v = (type) x; \
memcpy(env->buf_base + env->buf_pos, \
&v, sizeof(v)); \
} else \
packerror("error converting %s to %s: %s", \
arg, #type, strerror(errno)); \
} else \
packerror("out of arguments"); \
} \
} while(0)
#define GETUARG(env, type, maxv) do { \
if ((env)->buf_base) { \
char *arg = packenv_get(env); \
if (arg) { \
uintmax_t x; \
if (getunum(arg, maxv, &x) == 0) { \
type v = (type) x; \
memcpy(env->buf_base + env->buf_pos, \
&v, sizeof(v)); \
} else \
packerror("error converting %s to %s: %s", \
arg, #type, strerror(errno)); \
} else \
packerror("out of arguments"); \
} \
} while(0)
static void
Z_packer(struct packenv *env, int rep)
{
if (env->buf_base) {
char *arg = packenv_get(env);
memset(env->buf_base + env->buf_pos, 0, rep);
if (arg) {
size_t len = strlen(arg);
if (len > rep - 1)
len = rep - 1;
memcpy(env->buf_base + env->buf_pos, arg, len);
} else
packerror("out of arguments");
}
env->buf_pos += rep;
}
static void
Z_unpacker(struct packenv *env, int rep)
{
fprintf(env->fp, "%-*.*s", rep-1, rep-1, env->buf_base + env->buf_pos);
env->buf_pos += rep;
}
static void
c_packer(struct packenv *env, int rep)
{
if (env->buf_base) {
char *arg = packenv_get(env);
if (arg)
env->buf_base[env->buf_pos] = *arg;
else
packerror("out of arguments");
}
env->buf_pos++;
}
static void
c_unpacker(struct packenv *env, int rep)
{
fprintf(env->fp, "%c", env->buf_base[env->buf_pos++]);
}
static void
s_packer(struct packenv *env, int rep)
{
GETSARG(env, int16_t, INT16_MIN, INT16_MAX);
env->buf_pos += sizeof(int16_t);
}
static void
s_unpacker(struct packenv *env, int rep)
{
printsnum(env->fp, *(int16_t *) (env->buf_base + env->buf_pos));
env->buf_pos += sizeof(int16_t);
}
static void
S_packer(struct packenv *env, int rep)
{
GETUARG(env, uint16_t, UINT16_MAX);
env->buf_pos += sizeof(uint16_t);
}
static void
S_unpacker(struct packenv *env, int rep)
{
printunum(env->fp, *(uint16_t *)(env->buf_base + env->buf_pos));
env->buf_pos += sizeof(uint16_t);
}
static void
l_packer(struct packenv *env, int rep)
{
GETSARG(env, int32_t, INT32_MIN, INT32_MAX);
env->buf_pos += sizeof(int32_t);
}
static void
l_unpacker(struct packenv *env, int rep)
{
printsnum(env->fp, *(int32_t *)(env->buf_base + env->buf_pos));
env->buf_pos += sizeof(int32_t);
}
static void
L_packer(struct packenv *env, int rep)
{
GETUARG(env, uint32_t, UINT32_MAX);
env->buf_pos += sizeof(uint32_t);
}
static void
L_unpacker(struct packenv *env, int rep)
{
printunum(env->fp, *(uint32_t *)(env->buf_base + env->buf_pos));
env->buf_pos += sizeof(uint32_t);
}
static void
q_packer(struct packenv *env, int rep)
{
GETSARG(env, int64_t, INT64_MIN, INT64_MAX);
env->buf_pos += sizeof(int64_t);
}
static void
q_unpacker(struct packenv *env, int rep)
{
printsnum(env->fp, *(int64_t *)(env->buf_base + env->buf_pos));
env->buf_pos += sizeof(int64_t);
}
static void
Q_packer(struct packenv *env, int rep)
{
GETUARG(env, uint64_t, UINT64_MAX);
env->buf_pos += sizeof(uint64_t);
}
static void
Q_unpacker(struct packenv *env, int rep)
{
printunum(env->fp, *(uint64_t *)(env->buf_base + env->buf_pos));
env->buf_pos += sizeof(uint64_t);
}
static void
i_packer(struct packenv *env, int rep)
{
GETSARG(env, int, INT_MIN, INT_MAX);
env->buf_pos += sizeof(int);
}
static void
i_unpacker(struct packenv *env, int rep)
{
printsnum(env->fp, *(int *)(env->buf_base + env->buf_pos));
env->buf_pos += sizeof(int);
}
static void
I_packer(struct packenv *env, int rep)
{
GETUARG(env, unsigned, UINT_MAX);
env->buf_pos += sizeof(unsigned);
}
static void
I_unpacker(struct packenv *env, int rep)
{
printunum(env->fp, *(unsigned *)(env->buf_base + env->buf_pos));
env->buf_pos += sizeof(unsigned);
}
/* n - An unsigned short (16-bit) in "network" (big-endian) order. */
static void
n_packer(struct packenv *env, int rep)
{
if (env->buf_base) {
char *arg = packenv_get(env);
if (arg) {
uintmax_t x;
if (getunum(arg, UINT16_MAX, &x) == 0) {
uint16_t v = htons((uint16_t) x);
memcpy(env->buf_base + env->buf_pos,
&v, sizeof(v));
} else
packerror("error converting %s to %s: %s",
arg, "uint16_t (network order)",
strerror(errno));
} else
packerror("out of arguments");
}
env->buf_pos += sizeof(uint16_t);
}
static void
n_unpacker(struct packenv *env, int rep)
{
uint16_t v = ntohs(*(uint16_t *)(env->buf_base + env->buf_pos));
printunum(env->fp, v);
env->buf_pos += sizeof(uint16_t);
}
/* N - An unsigned long (32-bit) in "network" (big-endian) order. */
static void
N_packer(struct packenv *env, int rep)
{
if (env->buf_base) {
char *arg = packenv_get(env);
if (arg) {
uintmax_t x;
if (getunum(arg, UINT32_MAX, &x) == 0) {
uint32_t v = htonl((uint32_t) x);
memcpy(env->buf_base + env->buf_pos,
&v, sizeof(v));
} else
packerror("error converting %s to %s: %s",
arg, "uint32_t (network order)",
strerror(errno));
} else
packerror("out of arguments");
}
env->buf_pos += sizeof(uint32_t);
}
static void
N_unpacker(struct packenv *env, int rep)
{
uint32_t v = ntohl(*(uint32_t *)(env->buf_base + env->buf_pos));
printunum(env->fp, v);
env->buf_pos += sizeof(uint32_t);
}
/* v - An unsigned short (16-bit) in "VAX" (little-endian) order. */
static void
v_packer(struct packenv *env, int rep)
{
if (env->buf_base) {
char *arg = packenv_get(env);
if (arg) {
uintmax_t x;
if (getunum(arg, UINT16_MAX, &x) == 0) {
uint16_t v = ntohs((uint16_t) x);
memcpy(env->buf_base + env->buf_pos,
&v, sizeof(v));
} else
packerror("error converting %s to %s: %s",
arg, "uint16_t (host order)",
strerror(errno));
} else
packerror("out of arguments");
}
env->buf_pos += sizeof(uint16_t);
}
static void
v_unpacker(struct packenv *env, int rep)
{
uint16_t v = htons(*(uint16_t *)(env->buf_base + env->buf_pos));
printunum(env->fp, v);
env->buf_pos += sizeof(uint16_t);
}
/* V - An unsigned long (32-bit) in "VAX" (little-endian) order. */
static void
V_packer(struct packenv *env, int rep)
{
if (env->buf_base) {
char *arg = packenv_get(env);
if (arg) {
uintmax_t x;
if (getunum(arg, UINT32_MAX, &x) == 0) {
uint32_t v = ntohl((uint32_t) x);
memcpy(env->buf_base + env->buf_pos,
&v, sizeof(v));
} else
packerror("error converting %s to %s: %s",
arg, "uint32_t (host order)",
strerror(errno));
} else
packerror("out of arguments");
}
env->buf_pos += sizeof(uint32_t);
}
static void
V_unpacker(struct packenv *env, int rep)
{
uint32_t v = htonl(*(uint32_t *)(env->buf_base + env->buf_pos));
printunum(env->fp, v);
env->buf_pos += sizeof(uint32_t);
}
/* f - A single-precision float in native format. */
static void
f_packer(struct packenv *env, int rep)
{
if (env->buf_base) {
char *arg = packenv_get(env);
if (arg) {
float v;
char *p;
errno = 0;
v = strtof(arg, &p);
if (*p)
packerror("error converting %s to %s (near %s)",
arg, "float", p);
else if (errno)
packerror("error converting %s to %s: %s",
arg, "float", strerror(errno));
else
memcpy(env->buf_base + env->buf_pos,
&v, sizeof(v));
}
}
env->buf_pos += sizeof(float);
}
static void
f_unpacker(struct packenv *env, int rep)
{
float v = *(float *)(env->buf_base + env->buf_pos);
fprintf(env->fp, "%f", v); /* FIXME: Format? */
env->buf_pos += sizeof(uint32_t);
}
/* d - A double-precision float in native format. */
static void
d_packer(struct packenv *env, int rep)
{
if (env->buf_base) {
char *arg = packenv_get(env);
if (arg) {
double v;
char *p;
errno = 0;
v = strtod(arg, &p);
if (*p)
packerror("error converting %s to %s (near %s)",
arg, "double", p);
else if (errno)
packerror("error converting %s to %s: %s",
arg, "double", strerror(errno));
else
memcpy(env->buf_base + env->buf_pos,
&v, sizeof(v));
}
}
env->buf_pos += sizeof(double);
}
static void
d_unpacker(struct packenv *env, int rep)
{
double v = *(double *)(env->buf_base + env->buf_pos);
fprintf(env->fp, "%g", v); /* FIXME: Format? */
env->buf_pos += sizeof(double);
}
static void
x_packer(struct packenv *env, int rep)
{
if (env->buf_base)
env->buf_base[env->buf_pos] = 0;
env->buf_pos++;
}
static void
x_unpacker(struct packenv *env, int rep)
{
fputc(0, env->fp);
}
static void
X_packer(struct packenv *env, int rep)
{
if (env->buf_pos)
--env->buf_pos;
}
static void
at_packer(struct packenv *env, int rep)
{
if (env->buf_base)
memset(env->buf_base + env->buf_pos, 0, rep);
env->buf_pos += rep;
}
static void
at_unpacker(struct packenv *env, int rep)
{
env->buf_pos += rep;
}
static void
dot_packer(struct packenv *env, int rep)
{
if (env->buf_base) {
if (rep < env->buf_pos)
memset(env->buf_base + rep, 0, env->buf_pos - rep);
else
memset(env->buf_base + env->buf_pos, 0,
rep - env->buf_pos);
}
env->buf_pos = rep;
}
static void
dot_unpacker(struct packenv *env, int rep)
{
env->buf_pos = rep;
}
static struct packspec packspec[] = {
{ 'Z', F_REP, Z_packer, Z_unpacker },
{ 'c', 0, c_packer, c_unpacker },
{ 's', 0, s_packer, s_unpacker },
{ 'S', 0, S_packer, S_unpacker },
{ 'l', 0, l_packer, l_unpacker },
{ 'L', 0, L_packer, L_unpacker },
{ 'q', 0, q_packer, q_unpacker },
{ 'Q', 0, Q_packer, Q_unpacker },
{ 'i', 0, i_packer, i_unpacker },
{ 'I', 0, I_packer, I_unpacker },
{ 'n', 0, n_packer, n_unpacker },
{ 'N', 0, N_packer, N_unpacker },
{ 'v', 0, v_packer, v_unpacker },
{ 'V', 0, V_packer, V_unpacker },
{ 'f', 0, f_packer, f_unpacker },
{ 'd', 0, d_packer, d_unpacker },
{ 'x', 0, x_packer, x_unpacker },
{ 'X', 0, X_packer, X_packer },
{ '@', F_REP, at_packer, at_unpacker },
{ '.', F_REP, dot_packer, dot_unpacker },
{ 0 }
};
static int
getrep(const char *s, char const **endp, int *rep)
{
int n;
char *p;
if (*s == '[') {
if (s[1]) {
if (isdigit(s[1])) {
n = strtol(s + 1, &p, 10);
if (n <= 0) {
*endp = s;
return -1;
} else if (*p != ']') {
*endp = p;
return -1;
}
*rep = n;
*endp = p + 1;
} else {
*endp = s;
return -1;
}
} else {
*endp = s;
return -1;
}
} else if (isdigit(*s)) {
n = strtol(s, &p, 10);
if (n <= 0) {
*endp = s;
return -1;
}
*rep = n;
*endp = p;
} else {
*rep = 1;
*endp = s;
}
return 0;
}
struct packinst *
packcomp(const char *s, char **endp)
{
struct packinst *head = NULL, *tail = NULL, *pi;
struct packspec *ps;
int rep;
int ec = 0;
errno = 0;
while (*s) {
if (isspace(*s)) {
++s;
continue;
}
for (ps = packspec; ps->ch; ps++)
if (ps->ch == *s)
break;
if (!ps->ch) {
ec = EINVAL;
break;
}
if (getrep(s + 1, &s, &rep)) {
ec = EINVAL;
break;
}
pi = malloc(sizeof(*pi));
if (!pi) {
ec = ENOMEM;
break;
}
pi->next = NULL;
pi->spec = ps;
pi->rep = rep;
if (tail)
tail->next = pi;
else
head = pi;
tail = pi;
}
if (ec) {
packfree(head);
head = NULL;
errno = ec;
}
if (endp)
*endp = (char*) s;
return head;
}
struct packinst *
packdup(struct packinst *src)
{
struct packinst *head = NULL, *tail = NULL, *p;
for (; src; src = src->next) {
p = malloc(sizeof(*p));
if (!p) {
packfree(head);
errno = ENOMEM;
return NULL;
}
p->next = NULL;
p->spec = src->spec;
p->rep = src->rep;
p->cur = src->cur;
if (tail)
tail->next = p;
else
head = p;
tail = p;
}
return head;
}
void
packfree(struct packinst *pi)
{
while (pi) {
struct packinst *next = pi->next;
free(pi);
pi = next;
}
}
void
packin(struct packinst *pi, struct packenv *env)
{
int i;
for (; pi; pi = pi->next) {
if (pi->spec->flags & F_REP)
pi->spec->packer(env, pi->rep);
else
for (i = 0; i < pi->rep; i++)
pi->spec->packer(env, 1);
}
}
struct packinst *
packinnext(struct packinst *pi, struct packenv *env)
{
if (!pi)
return NULL;
if (pi->spec->flags & F_REP)
pi->spec->packer(env, pi->rep);
else {
pi->spec->packer(env, 1);
if (++pi->cur < pi->rep)
return pi;
}
return packinit(pi->next);
}
void
packout(struct packinst *pi, struct packenv *env)
{
int i;
for (; pi; pi = pi->next) {
if (pi->spec->flags & F_REP)
pi->spec->unpacker(env, pi->rep);
else
for (i = 0; i < pi->rep; i++) {
if (i > 0)
fputc(' ', env->fp);
pi->spec->unpacker(env, 1);
}
if (pi->next)
fputc(' ', env->fp);
}
}
size_t
packsize(struct packinst *pi)
{
struct packenv env;
memset(&env, 0, sizeof env);
packin(pi, &env);
return env.buf_pos;
}
struct packinst *
packinit(struct packinst *inst)
{
if (inst)
inst->cur = 0;
return inst;
}
struct packenv *
packenv_create(size_t size)
{
struct packenv *env = calloc(1, sizeof(*env));
if (env) {
env->buf_base = calloc(1, size);
if (!env->buf_base) {
free(env);
return NULL;
}
env->buf_size = size;
}
return env;
}
void
packenv_free(struct packenv *env)
{
if (!env)
return;
free(env->buf_base);
free(env);
}
void
packenv_init(struct packenv *env)
{
memset(env->buf_base, 0, env->buf_size);
env->buf_pos = 0;
}