zfs/module/zfs/jprint.c

452 lines
9.6 KiB
C

/*
* jprint.c
*/
/* If in ZFS */
#include <sys/zfs_context.h>
#include <sys/jprint.h>
/* If standalone */
/* #include "jprint.h" */
/* Do not support %g format. Just %d and %l for integers (if set) */
#define NO_DOUBLE 1
/* Use %g instead of %e for double format */
#define USE_G 1
/* Formats for int64_t and uint64_t */
#ifndef PRId64
#ifdef __FreeBSD__
#define PRId64 "ld" /* %D, int64_t */
#define PRIu64 "lu" /* %U, uint64_t */
#else /* Linux */
#define PRId64 "lld" /* %D, int64_t */
#define PRIu64 "llu" /* %U, uint64_t */
#endif
#endif
/* literal key length maximum */
#define KEYLEN 255
/* return error position (call number of jp_printf) */
int
jp_errorpos(jprint_t *jp)
{
return (jp->ncall);
}
/* return string for error code */
const char *
jp_errorstring(int err)
{
switch (err) {
case JPRINT_OK: return "jprint ok";
case JPRINT_BUF_FULL: return "jprint buffer full";
case JPRINT_NEST_ERROR: return "jprint nest error";
case JPRINT_STACK_FULL: return "jprint stack full";
case JPRINT_STACK_EMPTY: return "jprint stack empty";
case JPRINT_OPEN: return "jprint open";
case JPRINT_FMT: return "jprint format";
case JPRINT_NO_DOUBLE: return "jprint no double support";
default: return "jprint unknown error";
}
return ("jprint unknown error");
}
/* return error from jprint_t */
int
jp_error(jprint_t *jp)
{
return (jp->error);
}
/* open json using buffer of length buflen */
void
jp_open(jprint_t *jp, char *buffer, size_t buflen)
{
jp->buffer = jp->bufp = buffer;
jp->buflen = buflen;
jp->error = JPRINT_OK;
jp->ncall = 0;
jp->stackp = -1;
*buffer = '\0';
}
/* close json (return out of memory error) */
int
jp_close(jprint_t *jp)
{
if (jp->error != JPRINT_OK)
return (jp->error);
if (jp->stackp != -1)
jp->error = JPRINT_OPEN;
return (jp->error);
}
/* put character to json */
static void
jp_putc(jprint_t *jp, char c)
{
if (jp->error == JPRINT_OK) {
if ((jp->bufp - jp->buffer + 1) >= jp->buflen)
jp->error = JPRINT_BUF_FULL;
else {
*jp->bufp++ = c;
*jp->bufp = '\0';
}
}
}
/* put string to json */
static void
jp_puts(jprint_t *jp, char *s)
{
while (*s && (jp->error == JPRINT_OK))
jp_putc(jp, *s++);
}
/* put quoted string to json */
static void
jp_putsq(jprint_t *jp, char *s)
{
static const char *hex = "0123456789ABCDEF";
int c;
if (s == NULL) {
jp_puts(jp, (char *)"null");
return;
}
jp_putc(jp, '\"');
while (*s && (jp->error == JPRINT_OK)) {
c = (int)*s++;
/* formfeed, newline, return, tab, backspace */
if (c == 12)
jp_puts(jp, (char *)"\\f");
else if (c == 10)
jp_puts(jp, (char *)"\\n");
else if (c == 13)
jp_puts(jp, (char *)"\\r");
else if (c == 9)
jp_puts(jp, (char *)"\\t");
else if (c == 8)
jp_puts(jp, (char *)"\\b");
/*
* all characters from 0x00 to 0x1f, and 0x7f are
* escaped as: \u00xx
*/
else if (((0 <= c) && (c <= 0x1f)) || (c == 0x7f)) {
jp_puts(jp, (char *)"\\u00");
jp_putc(jp, hex[(c >> 4) & 0x0f]);
jp_putc(jp, hex[c & 0x0f]);
/* * " \ / */
} else if (c == '"')
jp_puts(jp, (char *)"\\\"");
else if (c == '\\')
jp_puts(jp, (char *)"\\\\");
else if (c == '/')
jp_puts(jp, (char *)"\\/");
/*
* all other printable characters ' ' to '~', and
* any utf-8 sequences (high bit set):
* 1xxxxxxx 10xxxxxx ...
* is a utf-8 sequence (10xxxxxx may occur 1 to 3 times).
* Note that this is simply distinguished here as high
* bit set.
*/
else
jp_putc(jp, (char)c);
}
jp_putc(jp, '\"');
}
/* put out key if object open. error if nothing open */
static int
jp_key(jprint_t *jp, char *key)
{
if (jp->error != JPRINT_OK)
goto err;
/* at top level, no frame exists yet, no error */
if (jp->stackp == -1)
goto err;
/* stackp has been "popped" too many times */
if (jp->stackp < -1) {
jp->error = JPRINT_STACK_EMPTY;
goto err;
}
/* put comma separator in (both object and array) */
if (++jp->stack[jp->stackp].nelem > 1)
jp_putc(jp, ',');
/* if its in an object, put out the key and separator */
if (jp->stack[jp->stackp].type == JP_OBJECT) {
jp_putsq(jp, key);
jp_putc(jp, ':');
}
err:
return (jp->error);
}
/* printf to json */
int
jp_printf(jprint_t *jp, const char *fmt, ...)
{
char key[KEYLEN + 1];
int k, i;
va_list ap;
int n;
unsigned int u;
int64_t n64;
uint64_t u64;
boolean_t b;
char *s;
char *start = jp->bufp;
if (jp->error != JPRINT_OK)
return (-1);
++jp->ncall;
va_start(ap, fmt);
key[k = 0] = '\0';
while (*fmt && (jp->error == JPRINT_OK)) {
switch (*fmt) {
case '%':
++fmt;
switch (*fmt) {
case 'k': /* next parameter is key */
if (jp->stackp < 0) {
jp->error = JPRINT_STACK_EMPTY;
break;
}
s = va_arg(ap, char *);
if (strlen(s) <= KEYLEN)
strcpy(key, s);
else
jp->error = JPRINT_FMT;
break;
case 'd': /* next parameter is int */
if (jp->stackp < 0) {
jp->error = JPRINT_STACK_EMPTY;
break;
}
n = va_arg(ap, int);
i = snprintf(
jp->tmpbuf, sizeof (jp->tmpbuf),
"%d", n);
if (jp_key(jp, key) == JPRINT_OK) {
if ((i >= sizeof (jp->tmpbuf)) ||
(i < 0))
jp_puts(jp, (char *)"####");
else
jp_puts(jp, jp->tmpbuf);
}
key[k = 0] = '\0';
break;
case 'u': /* next parameter is unsigned int */
if (jp->stackp < 0) {
jp->error = JPRINT_STACK_EMPTY;
break;
}
u = va_arg(ap, unsigned int);
i = snprintf(
jp->tmpbuf, sizeof (jp->tmpbuf),
"%u", u);
if (jp_key(jp, key) == JPRINT_OK) {
if ((i >= sizeof (jp->tmpbuf)) ||
(i < 0))
jp_puts(jp, (char *)"####");
else
jp_puts(jp, jp->tmpbuf);
}
key[k = 0] = '\0';
break;
case 'U': /* next parameter is uint64_t */
if (jp->stackp < 0) {
jp->error = JPRINT_STACK_EMPTY;
break;
}
u64 = va_arg(ap, uint64_t);
i = snprintf(
jp->tmpbuf, sizeof (jp->tmpbuf),
"%" PRIu64, u64);
if (jp_key(jp, key) == JPRINT_OK) {
if ((i >= sizeof (jp->tmpbuf)) ||
(i < 0))
jp_puts(jp, (char *)"####");
else
jp_puts(jp, jp->tmpbuf);
}
key[k = 0] = '\0';
break;
case 'D': /* next parameter is int64_t */
if (jp->stackp < 0) {
jp->error = JPRINT_STACK_EMPTY;
break;
}
n64 = va_arg(ap, int64_t);
i = snprintf(
jp->tmpbuf, sizeof (jp->tmpbuf),
"%" PRId64, n64);
if (jp_key(jp, key) == JPRINT_OK) {
if ((i >= sizeof (jp->tmpbuf)) ||
(i < 0))
jp_puts(jp, (char *)"####");
else
jp_puts(jp, jp->tmpbuf);
}
key[k = 0] = '\0';
break;
case 's': /* next parameter is string */
if (jp->stackp < 0) {
jp->error = JPRINT_STACK_EMPTY;
break;
}
s = va_arg(ap, char *);
if (jp_key(jp, key) == JPRINT_OK)
jp_putsq(jp, s);
key[k = 0] = '\0';
break;
case 'g': /* next parameter is double */
#if NO_DOUBLE
jp->error = JPRINT_NO_DOUBLE;
#else
if (jp->stackp < 0) {
jp->error = JPRINT_STACK_EMPTY;
break;
}
double x;
x = va_arg(ap, double);
if (jp_key(jp, key) == JPRINT_OK) {
#if USE_G
/*
* if we have functional %g format,
* use it.
*/
i = snprintf(
jp->tmpbuf, sizeof (jp->tmpbuf),
"%g", x);
#else
/*
* double has 15 places:
* 1.<14 digits>e-308
*/
i = snprintf(
jp->tmpbuf, sizeof (jp->tmpbuf),
"%21.14e", x);
#endif
if ((i >= sizeof (jp->tmpbuf)) ||
(i < 0))
jp_puts(jp, (char *)"####");
else
jp_puts(jp, jp->tmpbuf);
}
#endif
key[k = 0] = '\0';
break;
case 'b': /* next parameter is boolean */
if (jp->stackp < 0) {
jp->error = JPRINT_STACK_EMPTY;
break;
}
if (jp_key(jp, key) == JPRINT_OK) {
b = (boolean_t)va_arg(ap, int);
s = b ?
(char *)"true" :
(char *)"false";
jp_puts(jp, s);
}
key[k = 0] = '\0';
break;
case '%': /* literal % */
if (k < KEYLEN) {
key[k++] = '%';
key[k] = '\0';
} else
jp->error = JPRINT_FMT;
break;
default:
jp->error = JPRINT_FMT;
}
break;
case '{': /* open object */
if (jp->stackp >= (JP_MAX_STACK - 1))
jp->error = JPRINT_STACK_FULL;
else {
(void) jp_key(jp, key);
++jp->stackp;
jp->stack[jp->stackp].type = JP_OBJECT;
jp->stack[jp->stackp].nelem = 0;
jp_putc(jp, '{');
}
break;
case '}': /* close object */
if (jp->stackp < 0)
jp->error = JPRINT_STACK_EMPTY;
else if (jp->stack[jp->stackp].type != JP_OBJECT)
jp->error = JPRINT_NEST_ERROR;
else {
--jp->stackp;
jp_putc(jp, '}');
}
break;
case '[': /* open array */
if (jp->stackp >= (JP_MAX_STACK - 1))
jp->error = JPRINT_STACK_FULL;
else {
(void) jp_key(jp, key);
++jp->stackp;
jp->stack[jp->stackp].type = JP_ARRAY;
jp->stack[jp->stackp].nelem = 0;
jp_putc(jp, '[');
}
break;
case ']': /* close array */
if (jp->stackp < 0)
jp->error = JPRINT_STACK_EMPTY;
else if (jp->stack[jp->stackp].type != JP_ARRAY)
jp->error = JPRINT_NEST_ERROR;
else {
--jp->stackp;
jp_putc(jp, ']');
}
break;
case ',': /* ,: space tab are ignored */
case ':':
case ' ':
case '\t':
break;
case '\\':
/* allow inclusion of ,: space tab to key */
if (fmt[1] == '\0')
jp->error = JPRINT_FMT;
else {
++fmt;
if (k < KEYLEN) {
key[k++] = *fmt;
key[k] = '\0';
} else
jp->error = JPRINT_FMT;
}
break;
default:
if (k < KEYLEN) {
key[k++] = *fmt;
key[k] = '\0';
} else
jp->error = JPRINT_FMT;
break;
}
++fmt;
}
va_end(ap);
if (jp->error != JPRINT_OK)
return (-1);
return (int)(jp->bufp - start);
}