Import a JSON parsing / formatting code from http://mjson.sourceforge.net/ with some API changes to better cope with libvirt's needs. Then add basic implementation of the JSON monitor for QEMU driver. * src/util/json.c, src/util/json.h: Import code for simple JSON parsing & formatting * src/libvirt_private.syms: Include all json parser symbols * src/qemu/qemu_conf.c: Support for settin 'control' attribute to turn on JSON protocol mode * src/qemu/qemu_monitor.c: Conditionally call either Text or JSON monitor command serialization code as appropriate * src/qemu/qemu_monitor_json.c, src/qemu/qemu_monitor_json.h: new JSON based command serialization code --- src/Makefile.am | 3 + src/libvirt_private.syms | 10 + src/qemu/qemu_conf.c | 13 +- src/qemu/qemu_conf.h | 3 + src/qemu/qemu_monitor.c | 202 ++- src/qemu/qemu_monitor_json.c | 797 +++++++++ src/qemu/qemu_monitor_json.h | 155 ++ src/util/json.c | 3904 ++++++++++++++++++++++++++++++++++++++++++ src/util/json.h | 311 ++++ 9 files changed, 5350 insertions(+), 48 deletions(-) create mode 100644 src/qemu/qemu_monitor_json.c create mode 100644 src/qemu/qemu_monitor_json.h create mode 100644 src/util/json.c create mode 100644 src/util/json.h diff --git a/src/Makefile.am b/src/Makefile.am index 9ed9bc3..b87e615 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -51,6 +51,7 @@ UTIL_SOURCES = \ util/event.c util/event.h \ util/hash.c util/hash.h \ util/iptables.c util/iptables.h \ + util/json.c util/json.h \ util/logging.c util/logging.h \ util/memory.c util/memory.h \ util/pci.c util/pci.h \ @@ -184,6 +185,8 @@ QEMU_DRIVER_SOURCES = \ qemu/qemu_monitor.c qemu/qemu_monitor.h \ qemu/qemu_monitor_text.c \ qemu/qemu_monitor_text.h \ + qemu/qemu_monitor_json.c \ + qemu/qemu_monitor_json.h \ qemu/qemu_driver.c qemu/qemu_driver.h UML_DRIVER_SOURCES = \ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 4f2f048..f0809f8 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -260,6 +260,16 @@ virRegisterDeviceMonitor; virRegisterSecretDriver; +# json.h +json_tree_to_string; +json_parse_document; +json_new_string; +json_new_object; +json_free_value; +json_insert_pair_into_object; +json_find_first_label; + + # logging.h virLogMessage; virLogGetNbFilters; diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 951a6c6..16bde05 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -1748,10 +1748,18 @@ int qemudBuildCommandLine(virConnectPtr conn, ADD_ARG_LIT("-nographic"); if (monitor_chr) { + /* XXX gross hack for testing */ +#if QEMU_WITH_JSON + char buf[4096] = "control,"; + + if (qemudBuildCommandLineChrDevStr(monitor_chr, buf + 8, sizeof(buf)-8) < 0) + goto error; +#else char buf[4096]; - if (qemudBuildCommandLineChrDevStr(monitor_chr, buf, sizeof(buf)) < 0) + if (qemudBuildCommandLineChrDevStr(monitor_chr, buf, sizeof(buf)-8) < 0) goto error; +#endif ADD_ARG_LIT("-monitor"); ADD_ARG_LIT(buf); @@ -1885,7 +1893,8 @@ int qemudBuildCommandLine(virConnectPtr conn, break; } - virBufferVSprintf(&opt, "file=%s", disk->src ? disk->src : ""); + if (disk->src) + virBufferVSprintf(&opt, "file=%s", disk->src ? disk->src : ""); virBufferVSprintf(&opt, ",if=%s", bus); if (disk->device == VIR_DOMAIN_DISK_DEVICE_CDROM) virBufferAddLit(&opt, ",media=cdrom"); diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index a6e68f8..65c6489 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -39,6 +39,9 @@ #define qemudDebug(fmt, ...) do {} while(0) +/* XXX gross hack for testing purposes */ +#define QEMU_WITH_JSON 0 + #define QEMUD_CPUMASK_LEN CPU_SETSIZE /* Internal flags to keep track of qemu command line capabilities */ diff --git a/src/qemu/qemu_monitor.c b/src/qemu/qemu_monitor.c index fb89f9d..875d339 100644 --- a/src/qemu/qemu_monitor.c +++ b/src/qemu/qemu_monitor.c @@ -30,6 +30,7 @@ #include "qemu_monitor.h" #include "qemu_monitor_text.h" +#include "qemu_monitor_json.h" #include "qemu_conf.h" #include "event.h" #include "virterror_internal.h" @@ -71,6 +72,8 @@ struct _qemuMonitor { unsigned eofcb: 1; /* If the monitor callback should free the closed monitor */ unsigned closed: 1; + + unsigned json: 1; }; static void qemuMonitorLock(qemuMonitorPtr mon) @@ -180,9 +183,14 @@ qemuMonitorIOProcess(qemuMonitorPtr mon) msg = mon->msg; VIR_DEBUG("Process %d", mon->bufferOffset); - len = qemuMonitorTextIOProcess(mon, - mon->buffer, mon->bufferOffset, - msg); + if (mon->json) + len = qemuMonitorJSONIOProcess(mon, + mon->buffer, mon->bufferOffset, + msg); + else + len = qemuMonitorTextIOProcess(mon, + mon->buffer, mon->bufferOffset, + msg); if (len < 0) { mon->lastErrno = errno; @@ -455,6 +463,7 @@ qemuMonitorOpen(virDomainObjPtr vm, mon->fd = -1; mon->vm = vm; mon->eofCB = eofCB; + mon->json = QEMU_WITH_JSON; qemuMonitorLock(mon); switch (vm->monitor_chr->type) { @@ -594,7 +603,10 @@ qemuMonitorStartCPUs(qemuMonitorPtr mon, qemuMonitorLock(mon); DEBUG("mon=%p, fd=%d", mon, mon->fd); - ret = qemuMonitorTextStartCPUs(mon, conn); + if (mon->json) + ret = qemuMonitorJSONStartCPUs(mon, conn); + else + ret = qemuMonitorTextStartCPUs(mon, conn); qemuMonitorUnlock(mon); return ret; } @@ -606,7 +618,10 @@ qemuMonitorStopCPUs(qemuMonitorPtr mon) { qemuMonitorLock(mon); DEBUG("mon=%p, fd=%d", mon, mon->fd); - ret = qemuMonitorTextStopCPUs(mon); + if (mon->json) + ret = qemuMonitorJSONStopCPUs(mon); + else + ret = qemuMonitorTextStopCPUs(mon); qemuMonitorUnlock(mon); return ret; } @@ -617,7 +632,10 @@ int qemuMonitorSystemPowerdown(qemuMonitorPtr mon) { qemuMonitorLock(mon); DEBUG("mon=%p, fd=%d", mon, mon->fd); - ret = qemuMonitorTextSystemPowerdown(mon); + if (mon->json) + ret = qemuMonitorJSONSystemPowerdown(mon); + else + ret = qemuMonitorTextSystemPowerdown(mon); qemuMonitorUnlock(mon); return ret; } @@ -630,7 +648,10 @@ int qemuMonitorGetCPUInfo(qemuMonitorPtr mon, qemuMonitorLock(mon); DEBUG("mon=%p, fd=%d", mon, mon->fd); - ret = qemuMonitorTextGetCPUInfo(mon, pids); + if (mon->json) + ret = qemuMonitorJSONGetCPUInfo(mon, pids); + else + ret = qemuMonitorTextGetCPUInfo(mon, pids); qemuMonitorUnlock(mon); return ret; } @@ -642,7 +663,10 @@ int qemuMonitorGetBalloonInfo(qemuMonitorPtr mon, qemuMonitorLock(mon); DEBUG("mon=%p, fd=%d", mon, mon->fd); - ret = qemuMonitorTextGetBalloonInfo(mon, currmem); + if (mon->json) + ret = qemuMonitorJSONGetBalloonInfo(mon, currmem); + else + ret = qemuMonitorTextGetBalloonInfo(mon, currmem); qemuMonitorUnlock(mon); return ret; } @@ -660,10 +684,16 @@ int qemuMonitorGetBlockStatsInfo(qemuMonitorPtr mon, qemuMonitorLock(mon); DEBUG("mon=%p, fd=%d dev=%s", mon, mon->fd, devname); - ret = qemuMonitorTextGetBlockStatsInfo(mon, devname, - rd_req, rd_bytes, - wr_req, wr_bytes, - errs); + if (mon->json) + ret = qemuMonitorJSONGetBlockStatsInfo(mon, devname, + rd_req, rd_bytes, + wr_req, wr_bytes, + errs); + else + ret = qemuMonitorTextGetBlockStatsInfo(mon, devname, + rd_req, rd_bytes, + wr_req, wr_bytes, + errs); qemuMonitorUnlock(mon); return ret; } @@ -676,7 +706,10 @@ int qemuMonitorSetVNCPassword(qemuMonitorPtr mon, qemuMonitorLock(mon); DEBUG("mon=%p, fd=%d", mon, mon->fd); - ret = qemuMonitorTextSetVNCPassword(mon, password); + if (mon->json) + ret = qemuMonitorJSONSetVNCPassword(mon, password); + else + ret = qemuMonitorTextSetVNCPassword(mon, password); qemuMonitorUnlock(mon); return ret; } @@ -689,7 +722,10 @@ int qemuMonitorSetBalloon(qemuMonitorPtr mon, qemuMonitorLock(mon); DEBUG("mon=%p, fd=%d newmem=%lu", mon, mon->fd, newmem); - ret = qemuMonitorTextSetBalloon(mon, newmem); + if (mon->json) + ret = qemuMonitorJSONSetBalloon(mon, newmem); + else + ret = qemuMonitorTextSetBalloon(mon, newmem); qemuMonitorUnlock(mon); return ret; } @@ -701,7 +737,10 @@ int qemuMonitorEjectMedia(qemuMonitorPtr mon, qemuMonitorLock(mon); DEBUG("mon=%p, fd=%d devname=%s", mon, mon->fd, devname); - ret = qemuMonitorTextEjectMedia(mon, devname); + if (mon->json) + ret = qemuMonitorJSONEjectMedia(mon, devname); + else + ret = qemuMonitorTextEjectMedia(mon, devname); qemuMonitorUnlock(mon); return ret; } @@ -716,7 +755,10 @@ int qemuMonitorChangeMedia(qemuMonitorPtr mon, DEBUG("mon=%p, fd=%d devname=%s newmedia=%s", mon, mon->fd, devname, newmedia); - ret = qemuMonitorTextChangeMedia(mon, devname, newmedia); + if (mon->json) + ret = qemuMonitorJSONChangeMedia(mon, devname, newmedia); + else + ret = qemuMonitorTextChangeMedia(mon, devname, newmedia); qemuMonitorUnlock(mon); return ret; } @@ -732,7 +774,10 @@ int qemuMonitorSaveVirtualMemory(qemuMonitorPtr mon, DEBUG("mon=%p, fd=%d offset=%llu length=%zu path=%s", mon, mon->fd, offset, length, path); - ret = qemuMonitorTextSaveVirtualMemory(mon, offset, length, path); + if (mon->json) + ret = qemuMonitorJSONSaveVirtualMemory(mon, offset, length, path); + else + ret = qemuMonitorTextSaveVirtualMemory(mon, offset, length, path); qemuMonitorUnlock(mon); return ret; } @@ -747,7 +792,10 @@ int qemuMonitorSavePhysicalMemory(qemuMonitorPtr mon, DEBUG("mon=%p, fd=%d offset=%llu length=%zu path=%s", mon, mon->fd, offset, length, path); - ret = qemuMonitorTextSavePhysicalMemory(mon, offset, length, path); + if (mon->json) + ret = qemuMonitorJSONSavePhysicalMemory(mon, offset, length, path); + else + ret = qemuMonitorTextSavePhysicalMemory(mon, offset, length, path); qemuMonitorUnlock(mon); return ret; } @@ -760,7 +808,10 @@ int qemuMonitorSetMigrationSpeed(qemuMonitorPtr mon, qemuMonitorLock(mon); DEBUG("mon=%p, fd=%d bandwidth=%lu", mon, mon->fd, bandwidth); - ret = qemuMonitorTextSetMigrationSpeed(mon, bandwidth); + if (mon->json) + ret = qemuMonitorJSONSetMigrationSpeed(mon, bandwidth); + else + ret = qemuMonitorTextSetMigrationSpeed(mon, bandwidth); qemuMonitorUnlock(mon); return ret; } @@ -775,10 +826,16 @@ int qemuMonitorGetMigrationStatus(qemuMonitorPtr mon, qemuMonitorLock(mon); DEBUG("mon=%p, fd=%d", mon, mon->fd); - ret = qemuMonitorTextGetMigrationStatus(mon, status, - transferred, - remaining, - total); + if (mon->json) + ret = qemuMonitorJSONGetMigrationStatus(mon, status, + transferred, + remaining, + total); + else + ret = qemuMonitorTextGetMigrationStatus(mon, status, + transferred, + remaining, + total); qemuMonitorUnlock(mon); return ret; } @@ -794,7 +851,10 @@ int qemuMonitorMigrateToHost(qemuMonitorPtr mon, DEBUG("mon=%p, fd=%d hostname=%s port=%d", mon, mon->fd, hostname, port); - ret = qemuMonitorTextMigrateToHost(mon, background, hostname, port); + if (mon->json) + ret = qemuMonitorJSONMigrateToHost(mon, background, hostname, port); + else + ret = qemuMonitorTextMigrateToHost(mon, background, hostname, port); qemuMonitorUnlock(mon); return ret; } @@ -810,7 +870,10 @@ int qemuMonitorMigrateToCommand(qemuMonitorPtr mon, DEBUG("mon=%p, fd=%d argv=%p target=%s", mon, mon->fd, argv, target); - ret = qemuMonitorTextMigrateToCommand(mon, background, argv, target); + if (mon->json) + ret = qemuMonitorJSONMigrateToCommand(mon, background, argv, target); + else + ret = qemuMonitorTextMigrateToCommand(mon, background, argv, target); qemuMonitorUnlock(mon); return ret; } @@ -824,7 +887,10 @@ int qemuMonitorMigrateToUnix(qemuMonitorPtr mon, DEBUG("mon=%p fd=%d unixfile=%s", mon, mon->fd, unixfile); - ret = qemuMonitorTextMigrateToUnix(mon, background, unixfile); + if (mon->json) + ret = qemuMonitorJSONMigrateToUnix(mon, background, unixfile); + else + ret = qemuMonitorTextMigrateToUnix(mon, background, unixfile); qemuMonitorUnlock(mon); return ret; } @@ -835,7 +901,10 @@ int qemuMonitorMigrateCancel(qemuMonitorPtr mon) qemuMonitorLock(mon); DEBUG("mon=%p fd=%d", mon, mon->fd); - ret = qemuMonitorTextMigrateCancel(mon); + if (mon->json) + ret = qemuMonitorJSONMigrateCancel(mon); + else + ret = qemuMonitorTextMigrateCancel(mon); qemuMonitorUnlock(mon); return ret; } @@ -847,7 +916,10 @@ int qemuMonitorAddUSBDisk(qemuMonitorPtr mon, qemuMonitorLock(mon); DEBUG("mon=%p, fd=%d path=%s", mon, mon->fd, path); - ret = qemuMonitorTextAddUSBDisk(mon, path); + if (mon->json) + ret = qemuMonitorJSONAddUSBDisk(mon, path); + else + ret = qemuMonitorTextAddUSBDisk(mon, path); qemuMonitorUnlock(mon); return ret; } @@ -861,7 +933,10 @@ int qemuMonitorAddUSBDeviceExact(qemuMonitorPtr mon, qemuMonitorLock(mon); DEBUG("mon=%p, fd=%d bus=%d dev=%d", mon, mon->fd, bus, dev); - ret = qemuMonitorTextAddUSBDeviceExact(mon, bus, dev); + if (mon->json) + ret = qemuMonitorJSONAddUSBDeviceExact(mon, bus, dev); + else + ret = qemuMonitorTextAddUSBDeviceExact(mon, bus, dev); qemuMonitorUnlock(mon); return ret; } @@ -875,7 +950,10 @@ int qemuMonitorAddUSBDeviceMatch(qemuMonitorPtr mon, DEBUG("mon=%p, fd=%d vendor=%d product=%d", mon, mon->fd, vendor, product); - ret = qemuMonitorTextAddUSBDeviceMatch(mon, vendor, product); + if (mon->json) + ret = qemuMonitorJSONAddUSBDeviceMatch(mon, vendor, product); + else + ret = qemuMonitorTextAddUSBDeviceMatch(mon, vendor, product); qemuMonitorUnlock(mon); return ret; } @@ -896,12 +974,20 @@ int qemuMonitorAddPCIHostDevice(qemuMonitorPtr mon, mon, mon->fd, hostDomain, hostBus, hostSlot, hostFunction); - ret = qemuMonitorTextAddPCIHostDevice(mon, hostDomain, - hostBus, hostSlot, - hostFunction, - guestDomain, - guestBus, - guestSlot); + if (mon->json) + ret = qemuMonitorJSONAddPCIHostDevice(mon, hostDomain, + hostBus, hostSlot, + hostFunction, + guestDomain, + guestBus, + guestSlot); + else + ret = qemuMonitorTextAddPCIHostDevice(mon, hostDomain, + hostBus, hostSlot, + hostFunction, + guestDomain, + guestBus, + guestSlot); qemuMonitorUnlock(mon); return ret; } @@ -919,8 +1005,12 @@ int qemuMonitorAddPCIDisk(qemuMonitorPtr mon, DEBUG("mon=%p, fd=%d path=%s bus=%s", mon, mon->fd, path, bus); - ret = qemuMonitorTextAddPCIDisk(mon, path, bus, - guestDomain, guestBus, guestSlot); + if (mon->json) + ret = qemuMonitorJSONAddPCIDisk(mon, path, bus, + guestDomain, guestBus, guestSlot); + else + ret = qemuMonitorTextAddPCIDisk(mon, path, bus, + guestDomain, guestBus, guestSlot); qemuMonitorUnlock(mon); return ret; } @@ -936,8 +1026,12 @@ int qemuMonitorAddPCINetwork(qemuMonitorPtr mon, qemuMonitorLock(mon); DEBUG("mon=%p, fd=%d nicstr=%s", mon, mon->fd, nicstr); - ret = qemuMonitorTextAddPCINetwork(mon, nicstr, guestDomain, - guestBus, guestSlot); + if (mon->json) + ret = qemuMonitorJSONAddPCINetwork(mon, nicstr, guestDomain, + guestBus, guestSlot); + else + ret = qemuMonitorTextAddPCINetwork(mon, nicstr, guestDomain, + guestBus, guestSlot); qemuMonitorUnlock(mon); return ret; } @@ -953,8 +1047,12 @@ int qemuMonitorRemovePCIDevice(qemuMonitorPtr mon, DEBUG("mon=%p, fd=%d domain=%d bus=%d slot=%d", mon, mon->fd, guestDomain, guestBus, guestSlot); - ret = qemuMonitorTextRemovePCIDevice(mon, guestDomain, - guestBus, guestSlot); + if (mon->json) + ret = qemuMonitorJSONRemovePCIDevice(mon, guestDomain, + guestBus, guestSlot); + else + ret = qemuMonitorTextRemovePCIDevice(mon, guestDomain, + guestBus, guestSlot); qemuMonitorUnlock(mon); return ret; } @@ -969,7 +1067,10 @@ int qemuMonitorSendFileHandle(qemuMonitorPtr mon, DEBUG("mon=%p, fd=%d fdname=%s fd=%d", mon, mon->fd, fdname, fd); - ret = qemuMonitorTextSendFileHandle(mon, fdname, fd); + if (mon->json) + ret = qemuMonitorJSONSendFileHandle(mon, fdname, fd); + else + ret = qemuMonitorTextSendFileHandle(mon, fdname, fd); qemuMonitorUnlock(mon); return ret; } @@ -983,7 +1084,10 @@ int qemuMonitorCloseFileHandle(qemuMonitorPtr mon, DEBUG("mon=%p, fd=%d fdname=%s", mon, mon->fd, fdname); - ret = qemuMonitorTextCloseFileHandle(mon, fdname); + if (mon->json) + ret = qemuMonitorJSONCloseFileHandle(mon, fdname); + else + ret = qemuMonitorTextCloseFileHandle(mon, fdname); qemuMonitorUnlock(mon); return ret; } @@ -997,7 +1101,10 @@ int qemuMonitorAddHostNetwork(qemuMonitorPtr mon, DEBUG("mon=%p, fd=%d netstr=%s", mon, mon->fd, netstr); - ret = qemuMonitorTextAddHostNetwork(mon, netstr); + if (mon->json) + ret = qemuMonitorJSONAddHostNetwork(mon, netstr); + else + ret = qemuMonitorTextAddHostNetwork(mon, netstr); qemuMonitorUnlock(mon); return ret; } @@ -1012,7 +1119,10 @@ int qemuMonitorRemoveHostNetwork(qemuMonitorPtr mon, DEBUG("mon=%p, fd=%d netname=%s", mon, mon->fd, netname); - ret = qemuMonitorTextRemoveHostNetwork(mon, vlan, netname); + if (mon->json) + ret = qemuMonitorJSONRemoveHostNetwork(mon, vlan, netname); + else + ret = qemuMonitorTextRemoveHostNetwork(mon, vlan, netname); qemuMonitorUnlock(mon); return ret; } diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c new file mode 100644 index 0000000..d23569c --- /dev/null +++ b/src/qemu/qemu_monitor_json.c @@ -0,0 +1,797 @@ +/* + * qemu_monitor_json.c: interaction with QEMU monitor console + * + * Copyright (C) 2006-2009 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange <berrange@xxxxxxxxxx> + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <poll.h> +#include <unistd.h> +#include <string.h> + +#include "qemu_monitor_json.h" +#include "qemu_conf.h" +#include "c-ctype.h" +#include "memory.h" +#include "logging.h" +#include "driver.h" +#include "datatypes.h" +#include "virterror_internal.h" +#include "json.h" + +#define VIR_FROM_THIS VIR_FROM_QEMU + + +#define LINE_ENDING "\r\n" + +static int +qemuMonitorJSONIOProcessLine(qemuMonitorPtr mon ATTRIBUTE_UNUSED, + const char *line, + qemuMonitorMessagePtr msg) +{ + json_t *obj = NULL; + json_t *val; + int ret = -1; + + VIR_DEBUG("Line [%s]", line); + + if (json_parse_document(&obj, line) != JSON_OK) { + VIR_DEBUG0("Parsing JSON string failed"); + errno = EINVAL; + goto cleanup; + } + + if (obj->type != JSON_OBJECT) { + VIR_DEBUG0("Parsed JSON string isn't an object"); + errno = EINVAL; + } + + if (json_find_first_label(obj, "QMP", &val) == JSON_OK) { + VIR_DEBUG0("Got QMP capabilities data"); + ret = 0; + goto cleanup; + } + + if (json_find_first_label(obj, "event", &val) == JSON_OK) { + VIR_DEBUG0("Got an event"); + ret = 0; + goto cleanup; + } + + if (msg) { + msg->rxBuffer = strdup(line); + msg->rxLength = strlen(line); + msg->finished = 1; + } else { + VIR_DEBUG("Ignoring unexpected JSON message [%s]", line); + } + + ret = 0; + +cleanup: + if (obj) + json_free_value(&obj); + return ret; +} + +int qemuMonitorJSONIOProcess(qemuMonitorPtr mon, + const char *data, + size_t len, + qemuMonitorMessagePtr msg) +{ + int used = 0; + VIR_DEBUG("Data %d bytes [%s]", len, data); + + while (used < len) { + char *nl = strstr(data + used, LINE_ENDING); + + if (nl) { + int got = nl - (data + used); + char *line = strndup(data + used, got); + used += got + strlen(LINE_ENDING); + + if (qemuMonitorJSONIOProcessLine(mon, line, msg) < 0) { + VIR_FREE(line); + return -1; + } + + VIR_FREE(line); + } else { + break; + } + } + + VIR_DEBUG("Total used %d", used); + return used; +} + +static int +qemuMonitorCommandWithFd(qemuMonitorPtr mon, + json_t *cmd, + int scm_fd, + json_t **reply) +{ + int ret = -1; + qemuMonitorMessage msg; + char *cmdstr = NULL; + + *reply = NULL; + + memset(&msg, 0, sizeof msg); + + if (json_tree_to_string(cmd, &cmdstr) != JSON_OK) { + virReportOOMError(NULL); + goto cleanup; + } + if (virAsprintf(&msg.txBuffer, "%s\r\n", cmdstr) < 0) { + virReportOOMError(NULL); + goto cleanup; + } + msg.txLength = strlen(msg.txBuffer); + msg.txFD = scm_fd; + + VIR_DEBUG("Send command '%s' for write with FD %d", cmdstr, scm_fd); + + ret = qemuMonitorSend(mon, &msg); + + VIR_DEBUG("Receive command reply ret=%d errno=%d %d bytes '%s'", + ret, msg.lastErrno, msg.rxLength, msg.rxBuffer); + + + /* To make life safer for callers, already ensure there's at least an empty string */ + if (msg.rxBuffer && ret == 0) { + if (json_parse_document(reply, msg.rxBuffer) != JSON_OK) { + qemudReportError(NULL, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + _("cannot parse JSON doc '%s'"), msg.rxBuffer); + goto cleanup; + } + } + + if (ret < 0) + virReportSystemError(NULL, msg.lastErrno, + _("cannot send monitor command '%s'"), cmdstr); + +cleanup: + VIR_FREE(cmdstr); + VIR_FREE(msg.txBuffer); + VIR_FREE(msg.rxBuffer); + + return ret; +} + + +static int +qemuMonitorCommand(qemuMonitorPtr mon, + json_t *cmd, + json_t **reply) { + return qemuMonitorCommandWithFd(mon, cmd, -1, reply); +} + + +static json_t * +qemuMonitorJSONMakeCommand(const char *cmdname, + json_t *args) +{ + json_t *obj, *tmp = NULL; + if (!(obj = json_new_object())) + goto no_memory; + + if (!(tmp = json_new_string(cmdname))) + goto no_memory; + + if (json_insert_pair_into_object(obj, "execute", tmp) != JSON_OK) + goto no_memory; + tmp = NULL; + + if (args && + json_insert_pair_into_object(obj, "arguments", args) != JSON_OK) + goto no_memory; + + return obj; + +no_memory: + virReportOOMError(NULL); + if (obj) + json_free_value(&obj); + if (tmp) + json_free_value(&tmp); + return NULL; +} + +int +qemuMonitorJSONStartCPUs(qemuMonitorPtr mon, + virConnectPtr conn ATTRIBUTE_UNUSED) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("cont", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int +qemuMonitorJSONStopCPUs(qemuMonitorPtr mon) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("stop", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int qemuMonitorJSONSystemPowerdown(qemuMonitorPtr mon) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("system_powerdown", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int qemuMonitorJSONGetCPUInfo(qemuMonitorPtr mon, + int **pids) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("info cpus", NULL); + json_t *reply = NULL; + + *pids = NULL; + + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + + +/* + * Returns: 0 if balloon not supported, +1 if balloon query worked + * or -1 on failure + */ +int qemuMonitorJSONGetBalloonInfo(qemuMonitorPtr mon, + unsigned long *currmem) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("info balloon", NULL); + json_t *reply = NULL; + + *currmem = 0; + + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int qemuMonitorJSONGetBlockStatsInfo(qemuMonitorPtr mon, + const char *devname ATTRIBUTE_UNUSED, + long long *rd_req, + long long *rd_bytes, + long long *wr_req, + long long *wr_bytes, + long long *errs) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("info blockstats", NULL); + json_t *reply = NULL; + + *rd_req = *rd_bytes = *wr_req = *wr_bytes = *errs = 0; + + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int qemuMonitorJSONSetVNCPassword(qemuMonitorPtr mon, + const char *password ATTRIBUTE_UNUSED) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("change vnc password", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + +/* + * Returns: 0 if balloon not supported, +1 if balloon adjust worked + * or -1 on failure + */ +int qemuMonitorJSONSetBalloon(qemuMonitorPtr mon, + unsigned long newmem ATTRIBUTE_UNUSED) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("balloon", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + +int qemuMonitorJSONEjectMedia(qemuMonitorPtr mon, + const char *devname ATTRIBUTE_UNUSED) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("eject", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int qemuMonitorJSONChangeMedia(qemuMonitorPtr mon, + const char *devname ATTRIBUTE_UNUSED, + const char *newmedia ATTRIBUTE_UNUSED) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("change", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + +static int qemuMonitorJSONSaveMemory(qemuMonitorPtr mon, + const char *cmdtype, + unsigned long long offset ATTRIBUTE_UNUSED, + size_t length ATTRIBUTE_UNUSED, + const char *path ATTRIBUTE_UNUSED) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand(cmdtype, NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int qemuMonitorJSONSaveVirtualMemory(qemuMonitorPtr mon, + unsigned long long offset, + size_t length, + const char *path) +{ + return qemuMonitorJSONSaveMemory(mon, "memsave", offset, length, path); +} + +int qemuMonitorJSONSavePhysicalMemory(qemuMonitorPtr mon, + unsigned long long offset, + size_t length, + const char *path) +{ + return qemuMonitorJSONSaveMemory(mon, "pmemsave", offset, length, path); +} + + +int qemuMonitorJSONSetMigrationSpeed(qemuMonitorPtr mon, + unsigned long bandwidth ATTRIBUTE_UNUSED) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("migrate_set_speed", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + +int qemuMonitorJSONGetMigrationStatus(qemuMonitorPtr mon, + int *status, + unsigned long long *transferred, + unsigned long long *remaining, + unsigned long long *total) { + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("info migration", NULL); + json_t *reply = NULL; + + *status = 0; + *transferred = *remaining = *total = 0; + + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int qemuMonitorJSONMigrateToHost(qemuMonitorPtr mon, + int background ATTRIBUTE_UNUSED, + const char *hostname ATTRIBUTE_UNUSED, + int port ATTRIBUTE_UNUSED) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("migrate", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int qemuMonitorJSONMigrateToCommand(qemuMonitorPtr mon, + int background ATTRIBUTE_UNUSED, + const char * const *argv ATTRIBUTE_UNUSED, + const char *target ATTRIBUTE_UNUSED) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("migrate", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + +int qemuMonitorJSONMigrateToUnix(qemuMonitorPtr mon, + int background ATTRIBUTE_UNUSED, + const char *unixfile ATTRIBUTE_UNUSED) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("migrate", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + +int qemuMonitorJSONMigrateCancel(qemuMonitorPtr mon) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("migrate", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + +int qemuMonitorJSONAddUSBDisk(qemuMonitorPtr mon, + const char *path ATTRIBUTE_UNUSED) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("usb_add", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int qemuMonitorJSONAddUSBDeviceExact(qemuMonitorPtr mon, + int bus ATTRIBUTE_UNUSED, + int dev ATTRIBUTE_UNUSED) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("usb_add", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; + +} + +int qemuMonitorJSONAddUSBDeviceMatch(qemuMonitorPtr mon, + int vendor ATTRIBUTE_UNUSED, + int product ATTRIBUTE_UNUSED) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("usb_add", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int qemuMonitorJSONAddPCIHostDevice(qemuMonitorPtr mon, + unsigned hostDomain ATTRIBUTE_UNUSED, + unsigned hostBus ATTRIBUTE_UNUSED, + unsigned hostSlot ATTRIBUTE_UNUSED, + unsigned hostFunction ATTRIBUTE_UNUSED, + unsigned *guestDomain, + unsigned *guestBus, + unsigned *guestSlot) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("pci_add", NULL); + json_t *reply = NULL; + + *guestDomain = *guestBus = *guestSlot = 0; + + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int qemuMonitorJSONAddPCIDisk(qemuMonitorPtr mon, + const char *path ATTRIBUTE_UNUSED, + const char *bus ATTRIBUTE_UNUSED, + unsigned *guestDomain, + unsigned *guestBus, + unsigned *guestSlot) { + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("pci_add", NULL); + json_t *reply = NULL; + + *guestDomain = *guestBus = *guestSlot = 0; + + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int qemuMonitorJSONAddPCINetwork(qemuMonitorPtr mon, + const char *nicstr ATTRIBUTE_UNUSED, + unsigned *guestDomain, + unsigned *guestBus, + unsigned *guestSlot) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("pci_add", NULL); + json_t *reply = NULL; + + *guestDomain = *guestBus = *guestSlot = 0; + + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int qemuMonitorJSONRemovePCIDevice(qemuMonitorPtr mon, + unsigned guestDomain ATTRIBUTE_UNUSED, + unsigned guestBus ATTRIBUTE_UNUSED, + unsigned guestSlot ATTRIBUTE_UNUSED) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("pci_del", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int qemuMonitorJSONSendFileHandle(qemuMonitorPtr mon, + const char *fdname ATTRIBUTE_UNUSED, + int fd) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("getfd", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommandWithFd(mon, cmd, fd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int qemuMonitorJSONCloseFileHandle(qemuMonitorPtr mon, + const char *fdname ATTRIBUTE_UNUSED) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("closefd", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int qemuMonitorJSONAddHostNetwork(qemuMonitorPtr mon, + const char *netstr ATTRIBUTE_UNUSED) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("host_net_add", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} + + +int qemuMonitorJSONRemoveHostNetwork(qemuMonitorPtr mon, + int vlan ATTRIBUTE_UNUSED, + const char *netname ATTRIBUTE_UNUSED) +{ + int ret; + json_t *cmd = qemuMonitorJSONMakeCommand("host_net_remove", NULL); + json_t *reply = NULL; + if (!cmd) + return -1; + + ret = qemuMonitorCommand(mon, cmd, &reply); + + json_free_value(&cmd); + if (reply) + json_free_value(&reply); + return ret; +} diff --git a/src/qemu/qemu_monitor_json.h b/src/qemu/qemu_monitor_json.h new file mode 100644 index 0000000..a44158a --- /dev/null +++ b/src/qemu/qemu_monitor_json.h @@ -0,0 +1,155 @@ +/* + * qemu_monitor_json.h: interaction with QEMU monitor console + * + * Copyright (C) 2006-2009 Red Hat, Inc. + * Copyright (C) 2006 Daniel P. Berrange + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange <berrange@xxxxxxxxxx> + */ + + +#ifndef QEMU_MONITOR_JSON_H +#define QEMU_MONITOR_JSON_H + +#include "internal.h" + +#include "qemu_monitor.h" + +int qemuMonitorJSONIOProcess(qemuMonitorPtr mon, + const char *data, + size_t len, + qemuMonitorMessagePtr msg); + +int qemuMonitorJSONStartCPUs(qemuMonitorPtr mon, + virConnectPtr conn); +int qemuMonitorJSONStopCPUs(qemuMonitorPtr mon); + +int qemuMonitorJSONSystemPowerdown(qemuMonitorPtr mon); + +int qemuMonitorJSONGetCPUInfo(qemuMonitorPtr mon, + int **pids); +int qemuMonitorJSONGetBalloonInfo(qemuMonitorPtr mon, + unsigned long *currmem); +int qemuMonitorJSONGetBlockStatsInfo(qemuMonitorPtr mon, + const char *devname, + long long *rd_req, + long long *rd_bytes, + long long *wr_req, + long long *wr_bytes, + long long *errs); + + +int qemuMonitorJSONSetVNCPassword(qemuMonitorPtr mon, + const char *password); +int qemuMonitorJSONSetBalloon(qemuMonitorPtr mon, + unsigned long newmem); + +int qemuMonitorJSONEjectMedia(qemuMonitorPtr mon, + const char *devname); +int qemuMonitorJSONChangeMedia(qemuMonitorPtr mon, + const char *devname, + const char *newmedia); + + +int qemuMonitorJSONSaveVirtualMemory(qemuMonitorPtr mon, + unsigned long long offset, + size_t length, + const char *path); +int qemuMonitorJSONSavePhysicalMemory(qemuMonitorPtr mon, + unsigned long long offset, + size_t length, + const char *path); + +int qemuMonitorJSONSetMigrationSpeed(qemuMonitorPtr mon, + unsigned long bandwidth); + +int qemuMonitorJSONGetMigrationStatus(qemuMonitorPtr mon, + int *status, + unsigned long long *transferred, + unsigned long long *remaining, + unsigned long long *total); + +int qemuMonitorJSONMigrateToHost(qemuMonitorPtr mon, + int background, + const char *hostname, + int port); + +int qemuMonitorJSONMigrateToCommand(qemuMonitorPtr mon, + int background, + const char * const *argv, + const char *target); + +int qemuMonitorJSONMigrateToUnix(qemuMonitorPtr mon, + int background, + const char *unixfile); + +int qemuMonitorJSONMigrateCancel(qemuMonitorPtr mon); + +int qemuMonitorJSONAddUSBDisk(qemuMonitorPtr mon, + const char *path); + +int qemuMonitorJSONAddUSBDeviceExact(qemuMonitorPtr mon, + int bus, + int dev); +int qemuMonitorJSONAddUSBDeviceMatch(qemuMonitorPtr mon, + int vendor, + int product); + + +int qemuMonitorJSONAddPCIHostDevice(qemuMonitorPtr mon, + unsigned hostDomain, + unsigned hostBus, + unsigned hostSlot, + unsigned hostFunction, + unsigned *guestDomain, + unsigned *guestBus, + unsigned *guestSlot); + +int qemuMonitorJSONAddPCIDisk(qemuMonitorPtr mon, + const char *path, + const char *bus, + unsigned *guestDomain, + unsigned *guestBus, + unsigned *guestSlot); + +int qemuMonitorJSONAddPCINetwork(qemuMonitorPtr mon, + const char *nicstr, + unsigned *guestDomain, + unsigned *guestBus, + unsigned *guestSlot); + +int qemuMonitorJSONRemovePCIDevice(qemuMonitorPtr mon, + unsigned guestDomain, + unsigned guestBus, + unsigned guestSlot); + + +int qemuMonitorJSONSendFileHandle(qemuMonitorPtr mon, + const char *fdname, + int fd); + +int qemuMonitorJSONCloseFileHandle(qemuMonitorPtr mon, + const char *fdname); + +int qemuMonitorJSONAddHostNetwork(qemuMonitorPtr mon, + const char *netstr); + +int qemuMonitorJSONRemoveHostNetwork(qemuMonitorPtr mon, + int vlan, + const char *netname); + +#endif /* QEMU_MONITOR_JSON_H */ diff --git a/src/util/json.c b/src/util/json.c new file mode 100644 index 0000000..69b4434 --- /dev/null +++ b/src/util/json.c @@ -0,0 +1,3904 @@ +/*************************************************************************** + * Copyright (C) 2007 by Rui Maciel * + * rui.maciel@xxxxxxxxx * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Library General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program 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 Library General Public * + * License along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include <config.h> + +#include <stdlib.h> +#include <stdio.h> + +#include "json.h" +#include "memory.h" + +enum LEX_VALUE +{ LEX_MORE = 0, + LEX_INVALID_CHARACTER, + LEX_TRUE, + LEX_FALSE, + LEX_NULL, + LEX_BEGIN_OBJECT, + LEX_END_OBJECT, + LEX_BEGIN_ARRAY, + LEX_END_ARRAY, + LEX_NAME_SEPARATOR, + LEX_VALUE_SEPARATOR, + LEX_STRING, + LEX_NUMBER, + LEX_ERROR, + LEX_MEMORY +}; + + +/* rc_string part */ + +#define RSTRING_INCSTEP 5 +#define RSTRING_DEFAULT 8 + +enum rui_string_error_codes +{ RS_MEMORY, RS_OK = 1, RS_UNKNOWN }; + +typedef enum rui_string_error_codes rstring_code; + + +static rcstring * +rcs_create (size_t length) +{ + rcstring *rcs; + if (VIR_ALLOC(rcs) < 0) + return NULL; + + rcs->max = length; + rcs->length = 0; + + if (VIR_ALLOC_N(rcs->text, rcs->max + 1) < 0) + { + VIR_FREE(rcs); + return NULL; + } + rcs->text[0] = '\0'; + + return rcs; +} + + +static void +rcs_free (rcstring ** rcs) +{ + if (*rcs != NULL) + { + VIR_FREE((*rcs)->text); + VIR_FREE(*rcs); + } + +} + + +static rstring_code +rcs_resize (rcstring * rcs, size_t length) +{ + if (VIR_REALLOC_N(rcs->text, length + 1) < 0) + { + VIR_FREE(rcs); + return RS_MEMORY; + } + rcs->max = length; + rcs->text[rcs->max] = '\0'; + return RS_OK; +} + + +static rstring_code +rcs_catcs (rcstring * pre, const char *pos, const size_t length) +{ + if (pre->max < pre->length + length) + { + if (rcs_resize (pre, pre->length + length + RSTRING_INCSTEP) != RS_OK) + return RS_MEMORY; + } + strncpy (pre->text + pre->length, pos, length); + pre->text[pre->length + length] = '\0'; + pre->length += length; + return RS_OK; +} + + +static rstring_code +rcs_catc (rcstring * pre, const char c) +{ + if (pre->max <= pre->length) + { + if (rcs_resize (pre, pre->max + RSTRING_INCSTEP) != RS_OK) + return RS_MEMORY; + } + pre->text[pre->length] = c; + pre->length++; + pre->text[pre->length] = '\0'; + return RS_OK; +} + + +static char * +rcs_unwrap (rcstring * rcs) +{ + char *out; + + if (rcs->text == NULL) + out = NULL; + else + { + if (VIR_REALLOC_N(rcs->text, strlen(rcs->text)+1) < 0) + {} /* Ignore error to shrink */ + out = rcs->text; + } + + VIR_FREE(rcs); + return out; +} + + + +static size_t +rcs_length (rcstring * rcs) +{ + /*TODO account for UTF8 */ + return rcs->length; +} + + +/* end of rc_string part */ + + +enum json_error +json_stream_parse (FILE * file, json_t ** document) +{ + char buffer[1024]; /* hard-coded value */ + unsigned int error = JSON_INCOMPLETE_DOCUMENT; + + struct json_parsing_info state; + + json_jpi_init (&state); /* initializes the json_parsing_info object */ + + while ((error == JSON_WAITING_FOR_EOF) || (error == JSON_INCOMPLETE_DOCUMENT)) + { + if (fgets (buffer, 1024, file) != NULL) + { + switch (error = json_parse_fragment (&state, buffer)) + { + case JSON_OK: + case JSON_WAITING_FOR_EOF: + case JSON_INCOMPLETE_DOCUMENT: + break; + + default: + json_free_value (&state.cursor); + return error; + break; + } + } + else + { + if (error == JSON_WAITING_FOR_EOF) + error = JSON_OK; + else + { + /*TODO refine this error code */ + error = JSON_UNKNOWN_PROBLEM; + } + } + } + + if (error == JSON_OK) + { + *document = state.cursor; + } + + return error; +} + + +json_t * +json_new_value (const enum json_value_type type) +{ + json_t *new_object; + if (VIR_ALLOC(new_object) < 0) + return NULL; + + /* initialize members */ + new_object->text = NULL; + new_object->parent = NULL; + new_object->child = NULL; + new_object->child_end = NULL; + new_object->previous = NULL; + new_object->next = NULL; + new_object->type = type; + return new_object; +} + + +json_t * +json_new_string (const char *text) +{ + json_t *new_object; + size_t length; + + if (VIR_ALLOC(new_object) < 0) + return NULL; + + /* initialize members */ + length = strlen (text) + 1; + if (VIR_ALLOC_N(new_object->text, length) < 0) + { + VIR_FREE(new_object); + return NULL; + } + strncpy (new_object->text, text, length); + new_object->parent = NULL; + new_object->child = NULL; + new_object->child_end = NULL; + new_object->previous = NULL; + new_object->next = NULL; + new_object->type = JSON_STRING; + return new_object; +} + + +json_t * +json_new_number (const char *text) +{ + json_t *new_object; + size_t length; + + /* allocate memory for the new object */ + if (VIR_ALLOC(new_object) < 0) + return NULL; + + /* initialize members */ + length = strlen (text) + 1; + if (VIR_ALLOC_N(new_object->text, length) < 0) + { + VIR_FREE(new_object); + return NULL; + } + strncpy (new_object->text, text, length); + new_object->parent = NULL; + new_object->child = NULL; + new_object->child_end = NULL; + new_object->previous = NULL; + new_object->next = NULL; + new_object->type = JSON_NUMBER; + return new_object; +} + + +json_t * +json_new_object (void) +{ + return json_new_value (JSON_OBJECT); +} + + +json_t * +json_new_array (void) +{ + return json_new_value (JSON_ARRAY); +} + + +json_t * +json_new_null (void) +{ + return json_new_value (JSON_NULL); +} + + +json_t * +json_new_true (void) +{ + return json_new_value (JSON_TRUE); +} + + +json_t * +json_new_false (void) +{ + return json_new_value (JSON_FALSE); +} + + +void +json_free_value (json_t ** value) +{ + /* free each and every child node */ + if ((*value)->child != NULL) + { + json_t *i, *j; + i = (*value)->child_end; + while (i != NULL) + { + j = i->previous; + json_free_value (&i); /*TODO replace recursive solution with an iterative one */ + i = j; + } + } + + /* fixing sibling linked list connections */ + if ((*value)->previous && (*value)->next) + { + (*value)->previous->next = (*value)->next; + (*value)->next->previous = (*value)->previous; + } + else + { + if ((*value)->previous) + { + (*value)->previous->next = NULL; + } + if ((*value)->next) + { + (*value)->next->previous = NULL; + } + } + + /*fixing parent node connections */ + if ((*value)->parent) + { + /* fix the tree connection to the first node in the children's list */ + if ((*value)->parent->child == (*value)) + { + if ((*value)->next) + { + (*value)->parent->child = (*value)->next; /* the parent node always points to the first node in the children linked list */ + } + else + { + (*value)->parent->child = NULL; + } + } + + /* fix the tree connection to the last node in the children's list */ + if ((*value)->parent->child_end == (*value)) + { + if ((*value)->previous) + { + (*value)->parent->child_end = (*value)->previous; /* the parent node always points to the last node in the children linked list */ + } + else + { + (*value)->parent->child_end = NULL; + } + } + } + + /*finally, freeing the memory allocated for this value */ + VIR_FREE((*value)->text); + VIR_FREE(*value); +} + + +enum json_error +json_insert_child (json_t * parent, json_t * child) +{ + /*TODO change the child list from FIFO to LIFO, in order to get rid of the child_end pointer */ + /* enforce tree structure correctness */ + switch (parent->type) + { + case JSON_STRING: + /* a string accepts every JSON type as a child value */ + /* therefore, the sanity check must be performed on the child node */ + switch (child->type) + { + case JSON_STRING: + case JSON_NUMBER: + case JSON_TRUE: + case JSON_FALSE: + case JSON_NULL: + if (child->child != NULL) + return JSON_BAD_TREE_STRUCTURE; + break; + + case JSON_OBJECT: + case JSON_ARRAY: + break; + + default: + return JSON_BAD_TREE_STRUCTURE; /* this part should never be reached */ + break; + } + break; + + case JSON_OBJECT: /* JSON objects may only accept JSON string objects which already have child nodes of their own */ + if (child->type != JSON_STRING) + return JSON_BAD_TREE_STRUCTURE; + break; + + case JSON_ARRAY: + switch (child->type) + { + case JSON_STRING: + case JSON_TRUE: + case JSON_FALSE: + case JSON_NULL: + case JSON_NUMBER: + if (child->child) + return JSON_BAD_TREE_STRUCTURE; + break; + + case JSON_OBJECT: + case JSON_ARRAY: + break; + + default: + return JSON_BAD_TREE_STRUCTURE; + } + break; + + default: + return JSON_BAD_TREE_STRUCTURE; + } + + child->parent = parent; + if (parent->child) + { + child->previous = parent->child_end; + parent->child_end->next = child; + parent->child_end = child; + } + else + { + parent->child = child; + parent->child_end = child; + } + + return JSON_OK; +} + + +enum json_error +json_insert_pair_into_object (json_t * parent, const char *text_label, json_t * value) +{ + enum json_error error; + json_t *label; + + /* create label json_value */ + label = json_new_string (text_label); + if (label == NULL) + return JSON_MEMORY; + + /*insert value and check for error */ + error = json_insert_child (label, value); + if (error != JSON_OK) + return error; + /*insert value and check for error */ + error = json_insert_child (parent, label); + if (error != JSON_OK) + return error; + + return JSON_OK; +} + + +enum json_error +json_tree_to_string (json_t * root, char **text) +{ + json_t *cursor; + rcstring *output; + + cursor = root; + /* set up the output and temporary rwstrings */ + output = rcs_create (RSTRING_DEFAULT); + + /* start the convoluted fun */ +state1: /* open value */ + { + if ((cursor->previous) && (cursor != root)) /*if cursor is children and not root than it is a followup sibling */ + { + /* append comma */ + if (rcs_catc (output, ',') != RS_OK) + { + return JSON_MEMORY; + } + } + switch (cursor->type) + { + case JSON_STRING: + /* append the "text"\0, which means 1 + wcslen(cursor->text) + 1 + 1 */ + /* set the new output size */ + if (rcs_catc (output, '\"') != RS_OK) + { + return JSON_MEMORY; + } + if (rcs_catcs (output, cursor->text, strlen (cursor->text)) != RS_OK) + { + return JSON_MEMORY; + } + if (rcs_catc (output, '\"') != RS_OK) + { + return JSON_MEMORY; + } + + if (cursor->parent != NULL) + { + if (cursor->parent->type == JSON_OBJECT) /* cursor is label in label:value pair */ + { + /* error checking: if parent is object and cursor is string then cursor must have a single child */ + if (cursor->child != NULL) + { + if (rcs_catc (output, ':') != RS_OK) + { + return JSON_MEMORY; + } + } + else + { + /* malformed document tree: label without value in label:value pair */ + rcs_free (&output); + text = NULL; + return JSON_BAD_TREE_STRUCTURE; + } + } + } + else /* does not have a parent */ + { + if (cursor->child != NULL) /* is root label in label:value pair */ + { + if (rcs_catc (output, ':') != RS_OK) + { + return JSON_MEMORY; + } + } + else + { + /* malformed document tree: label without value in label:value pair */ + rcs_free (&output); + text = NULL; + return JSON_BAD_TREE_STRUCTURE; + } + } + break; + + case JSON_NUMBER: + /* must not have any children */ + /* set the new size */ + if (rcs_catcs (output, cursor->text, strlen (cursor->text)) != RS_OK) + { + return JSON_MEMORY; + } + goto state2; /* close value */ + break; + + case JSON_OBJECT: + if (rcs_catc (output, '{') != RS_OK) + { + return JSON_MEMORY; + } + + if (cursor->child) + { + cursor = cursor->child; + goto state1; /* open value */ + } + else + { + goto state2; /* close value */ + } + break; + + case JSON_ARRAY: + if (rcs_catc (output, '[') != RS_OK) + { + return JSON_MEMORY; + } + + if (cursor->child != NULL) + { + cursor = cursor->child; + goto state1; + } + else + { + goto state2; /* close value */ + } + break; + + case JSON_TRUE: + /* must not have any children */ + if (rcs_catcs (output, "true", 4) != RS_OK) + { + return JSON_MEMORY; + } + goto state2; /* close value */ + break; + + case JSON_FALSE: + /* must not have any children */ + if (rcs_catcs (output, "false", 5) != RS_OK) + { + return JSON_MEMORY; + } + goto state2; /* close value */ + break; + + case JSON_NULL: + /* must not have any children */ + if (rcs_catcs (output, "null", 4) != RS_OK) + { + return JSON_MEMORY; + } + goto state2; /* close value */ + break; + + default: + goto error; + } + if (cursor->child) + { + cursor = cursor->child; + goto state1; /* open value */ + } + else + { + /* does not have any children */ + goto state2; /* close value */ + } + } + +state2: /* close value */ + { + switch (cursor->type) + { + case JSON_OBJECT: + if (rcs_catc (output, '}') != RS_OK) + { + return JSON_MEMORY; + } + break; + + case JSON_ARRAY: + if (rcs_catc (output, ']') != RS_OK) + { + return JSON_MEMORY; + } + break; + + case JSON_STRING: + break; + case JSON_NUMBER: + break; + case JSON_TRUE: + break; + case JSON_FALSE: + break; + case JSON_NULL: + break; + default: + goto error; + } + if ((cursor->parent == NULL) || (cursor == root)) + { + goto end; + } + else if (cursor->next) + { + cursor = cursor->next; + goto state1; /* open value */ + } + else + { + cursor = cursor->parent; + goto state2; /* close value */ + } + } + +error: + { + rcs_free (&output); + return JSON_UNKNOWN_PROBLEM; + } + +end: + { + *text = rcs_unwrap (output); + return JSON_OK; + } +} + + +enum json_error +json_stream_output (FILE * file, json_t * root) +{ + json_t *cursor; + + cursor = root; + /* set up the output and temporary rwstrings */ + + /* start the convoluted fun */ +state1: /* open value */ + { + if ((cursor->previous) && (cursor != root)) /*if cursor is children and not root than it is a followup sibling */ + { + /* append comma */ + fprintf (file, ","); + } + switch (cursor->type) + { + case JSON_STRING: + /* append the "text"\0, which means 1 + wcslen(cursor->text) + 1 + 1 */ + /* set the new output size */ + fprintf (file, "\"%s\"", cursor->text); + + if (cursor->parent != NULL) + { + if (cursor->parent->type == JSON_OBJECT) /* cursor is label in label:value pair */ + { + /* error checking: if parent is object and cursor is string then cursor must have a single child */ + if (cursor->child != NULL) + { + if (fprintf (file, ":") != RS_OK) + { + return JSON_MEMORY; + } + } + else + { + /* malformed document tree: label without value in label:value pair */ + return JSON_BAD_TREE_STRUCTURE; + } + } + } + else /* does not have a parent */ + { + if (cursor->child != NULL) /* is root label in label:value pair */ + { + fprintf (file, ":"); + } + else + { + /* malformed document tree: label without value in label:value pair */ + return JSON_BAD_TREE_STRUCTURE; + } + } + break; + + case JSON_NUMBER: + /* must not have any children */ + /* set the new size */ + fprintf (file, "%s", cursor->text); + goto state2; /* close value */ + break; + + case JSON_OBJECT: + fprintf (file, "{"); + + if (cursor->child) + { + cursor = cursor->child; + goto state1; /* open value */ + } + else + { + goto state2; /* close value */ + } + break; + + case JSON_ARRAY: + fprintf (file, "["); + + if (cursor->child != NULL) + { + cursor = cursor->child; + goto state1; + } + else + { + goto state2; /* close value */ + } + break; + + case JSON_TRUE: + /* must not have any children */ + fprintf (file, "true"); + goto state2; /* close value */ + break; + + case JSON_FALSE: + /* must not have any children */ + fprintf (file, "false"); + goto state2; /* close value */ + break; + + case JSON_NULL: + /* must not have any children */ + fprintf (file, "null"); + goto state2; /* close value */ + break; + + default: + goto error; + } + if (cursor->child) + { + cursor = cursor->child; + goto state1; /* open value */ + } + else + { + /* does not have any children */ + goto state2; /* close value */ + } + } + +state2: /* close value */ + { + switch (cursor->type) + { + case JSON_OBJECT: + fprintf (file, "}"); + break; + + case JSON_ARRAY: + fprintf (file, "]"); + break; + + case JSON_STRING: + break; + case JSON_NUMBER: + break; + case JSON_TRUE: + break; + case JSON_FALSE: + break; + case JSON_NULL: + break; + default: + goto error; + } + if ((cursor->parent == NULL) || (cursor == root)) + { + goto end; + } + else if (cursor->next) + { + cursor = cursor->next; + goto state1; /* open value */ + } + else + { + cursor = cursor->parent; + goto state2; /* close value */ + } + } + +error: + { + return JSON_UNKNOWN_PROBLEM; + } + +end: + { + fprintf (file, "\n"); + return JSON_OK; + } +} + + +void +json_strip_white_spaces (char *text) +{ + size_t in, out, length; + int state; + + in = 0; + out = 0; + length = strlen (text); + state = 0; /* possible states: 0 -> document, 1 -> inside a string */ + + while (in < length) + { + switch (text[in]) + { + case '\x20': /* space */ + case '\x09': /* horizontal tab */ + case '\x0A': /* line feed or new line */ + case '\x0D': /* Carriage return */ + if (state == 1) + { + text[out++] = text[in]; + } + break; + + case '\"': + switch (state) + { + case 0: /* not inside a JSON string */ + state = 1; + break; + + case 1: /* inside a JSON string */ + if (text[in - 1] != '\\') + { + state = 0; + } + break; + } + text[out++] = text[in]; + break; + + default: + text[out++] = text[in]; + } + ++in; + } + text[out] = '\0'; +} + + +char * +json_format_string (const char *text) +{ + size_t pos = 0, text_length; + unsigned int indentation = 0; /* the current indentation level */ + unsigned int i; /* loop iterator variable */ + char loop; + + rcstring *output; + text_length = strlen (text); + + output = rcs_create (text_length); + while (pos < text_length) + { + switch (text[pos]) + { + case '\x20': + case '\x09': + case '\x0A': + case '\x0D': /* JSON insignificant white spaces */ + pos++; + break; + + case '{': + indentation++; + rcs_catcs (output, "{\n", 2); + for (i = 0; i < indentation; i++) + { + rcs_catc (output, '\t'); + } + pos++; + break; + + case '}': + indentation--; + rcs_catc (output, '\n'); + for (i = 0; i < indentation; i++) + { + rcs_catc (output, '\t'); + } + rcs_catc (output, '}'); + pos++; + break; + + case ':': + rcs_catcs (output, ": ", 2); + pos++; + break; + + case ',': + rcs_catcs (output, ",\n", 2); + for (i = 0; i < indentation; i++) + { + rcs_catc (output, '\t'); + } + pos++; + break; + + case '\"': /* open string */ + rcs_catc (output, text[pos]); + pos++; + loop = 1; /* inner string loop trigger is enabled */ + while (loop) + { + if (text[pos] == '\\') /* escaped sequence */ + { + rcs_catc (output, '\\'); + pos++; + if (text[pos] == '\"') /* don't consider a \" escaped sequence as an end of string */ + { + rcs_catc (output, '\"'); + pos++; + } + } + else if (text[pos] == '\"') /* reached end of string */ + { + loop = 0; + } + + rcs_catc (output, text[pos]); + + pos++; + if (pos >= text_length) + { + loop = 0; + } + } + break; + + default: + rcs_catc (output, text[pos]); + pos++; + break; + } + } + + return rcs_unwrap (output); +} + + +char * +json_escape (char *text) +{ + rcstring *output; + size_t i, length; + char buffer[6]; + + /* defining the temporary variables */ + length = strlen (text); + output = rcs_create (length); + if (output == NULL) + return NULL; + for (i = 0; i < length; i++) + { + if (text[i] == '\\') + { + rcs_catcs (output, "\\\\", 2); + } + else if (text[i] == '\"') + { + rcs_catcs (output, "\\\"", 2); + } + else if (text[i] == '/') + { + rcs_catcs (output, "\\/", 2); + } + else if (text[i] == '\b') + { + rcs_catcs (output, "\\b", 2); + } + else if (text[i] == '\f') + { + rcs_catcs (output, "\\f", 2); + } + else if (text[i] == '\n') + { + rcs_catcs (output, "\\n", 2); + } + else if (text[i] == '\r') + { + rcs_catcs (output, "\\r", 2); + } + else if (text[i] == '\t') + { + rcs_catcs (output, "\\t", 2); + } + else if (text[i] < 0) /* non-BMP character */ + { + rcs_catc (output, text[i]); + } + else if (text[i] < 0x20) + { + sprintf (buffer, "\\u%4.4x", text[i]); + rcs_catcs (output, buffer, 6); + } + else + { + rcs_catc (output, text[i]); + } + } + return rcs_unwrap (output); +} + + +void +json_jpi_init (struct json_parsing_info *jpi) +{ + jpi->state = 0; + jpi->lex_state = 0; + jpi->lex_text = NULL; + jpi->p = NULL; + jpi->cursor = NULL; + jpi->string_length_limit_reached = 0; +} + + +static int +lexer (const char *buffer, const char **p, unsigned int *state, rcstring ** text) +{ + if (*p == NULL) + *p = buffer; + + while (**p != '\0') + { + switch (*state) + { + + case 0: /* Root document */ + { + switch (*(*p)++) + { + case '\x20': /* space */ + case '\x09': /* horizontal tab */ + case '\x0A': /* line feed or new line */ + case '\x0D': /* Carriage return */ + break; + + case '{': + return LEX_BEGIN_OBJECT; + case '}': + return LEX_END_OBJECT; + case '[': + return LEX_BEGIN_ARRAY; + case ']': + return LEX_END_ARRAY; + case ':': + return LEX_NAME_SEPARATOR; + case ',': + return LEX_VALUE_SEPARATOR; + + case '\"': + *text = rcs_create (RSTRING_DEFAULT); + if (*text == NULL) + return LEX_MEMORY; + *state = 1; /* inside a JSON string */ + break; + + case 't': + *state = 7; /* true: 1 */ + break; + + case 'f': + *state = 10; /* false: 1 */ + break; + + case 'n': + *state = 14; /* false: 1 */ + break; + + case '-': + *text = rcs_create (RSTRING_DEFAULT); + if (*text == NULL) + return LEX_MEMORY; + if (rcs_catc (*text, '-') != RS_OK) + return LEX_MEMORY; + *state = 17; /* number: '0' */ + break; + + case '0': + *text = rcs_create (RSTRING_DEFAULT); + if (*text == NULL) + return LEX_MEMORY; + if (rcs_catc (*text, '0') != RS_OK) + return LEX_MEMORY; + *state = 18; /* number: '0' */ + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + *text = rcs_create (RSTRING_DEFAULT); + if (*text == NULL) + return LEX_MEMORY; + if (rcs_catc (*text, *(*p - 1)) != RS_OK) + return LEX_MEMORY; + *state = 19; /* number: decimal followup */ + break; + + + default: + return LEX_INVALID_CHARACTER; + } + } + break; + + case 1: /* inside a JSON string */ + { + switch (**p) + { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: /* line feed */ + case 11: + case 12: + case 13: /* carriage return */ + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + /* ASCII control characters can only be present in a JSON string if they are escaped. If not then the document is invalid */ + return LEX_INVALID_CHARACTER; + break; + + case '\"': /* close JSON string */ + /* it is expected that, in the routine that calls this function, text is set to NULL */ + *state = 0; + ++*p; + return LEX_STRING; + break; + + case '\\': + if (rcs_catc (*text, '\\') != RS_OK) + return LEX_MEMORY; + *state = 2; /* inside a JSON string: start escape sequence */ + break; + + default: + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + } + ++*p; + } + break; + + case 2: /* inside a JSON string: start escape sequence */ + { + switch (**p) + { + case '\\': + case '\"': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + *state = 1; /* inside a JSON string */ + break; + + case 'u': + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + *state = 3; /* inside a JSON string: escape unicode */ + break; + + default: + return LEX_INVALID_CHARACTER; + } + ++*p; + } + break; + + case 3: /*inside a JSON string: escape unicode */ + { + if ((**p >= 'a') && (**p <= 'f')) + { + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + *state = 4; /* inside a JSON string: escape unicode */ + } + else if ((**p >= 'A') && (**p <= 'F')) + { + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + *state = 4; /* inside a JSON string: escape unicode */ + } + else if ((**p >= '0') && (**p <= '9')) + { + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + *state = 4; /* inside a JSON string: escape unicode */ + } + else + return LEX_INVALID_CHARACTER; + ++*p; + } + break; + + case 4: /* inside a JSON string: escape unicode */ + { + if ((**p >= 'a') && (**p <= 'f')) + { + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + *state = 5; /* inside a JSON string: escape unicode */ + } + else if ((**p >= 'A') && (**p <= 'F')) + { + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + *state = 5; /* inside a JSON string: escape unicode */ + } + else if ((**p >= '0') && (**p <= '9')) + { + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + *state = 5; /* inside a JSON string: escape unicode */ + } + else + return LEX_INVALID_CHARACTER; + ++*p; + } + + case 5: /* inside a JSON string: escape unicode */ + { + if ((**p >= 'a') && (**p <= 'f')) + { + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + *state = 6; /* inside a JSON string: escape unicode */ + } + else if ((**p >= 'A') && (**p <= 'F')) + { + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + *state = 6; /* inside a JSON string: escape unicode */ + } + else if ((**p >= '0') && (**p <= '9')) + { + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + *state = 6; /* inside a JSON string: escape unicode */ + } + else + return LEX_INVALID_CHARACTER; + ++*p; + } + break; + + case 6: /* inside a JSON string: escape unicode */ + { + if ((**p >= 'a') && (**p <= 'f')) + { + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + *state = 1; /* inside a JSON string: escape unicode */ + } + else if ((**p >= 'A') && (**p <= 'F')) + { + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + *state = 1; /* inside a JSON string: escape unicode */ + } + else if ((**p >= '0') && (**p <= '9')) + { + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + *state = 1; /* inside a JSON string: escape unicode */ + } + else + return LEX_INVALID_CHARACTER; + ++*p; + } + break; + + case 7: /* true: 1 */ + { + switch (*(*p)++) + { + case 'r': + *state = 8; + break; + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + case 8: /* true: 2 */ + { + switch (*(*p)++) + { + case 'u': + *state = 9; + break; + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + case 9: /* true: 3 */ + { + switch (*(*p)++) + { + case 'e': + *state = 0; + return LEX_TRUE; + break; + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + case 10: /* false: 1 */ + { + switch (*(*p)++) + { + case 'a': + *state = 11; + break; + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + case 11: /* false: 2 */ + { + switch (*(*p)++) + { + case 'l': + *state = 12; + break; + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + case 12: /* false: 3 */ + { + switch (*(*p)++) + { + case 's': + *state = 13; + break; + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + case 13: /* false: 4 */ + { + switch (*(*p)++) + { + case 'e': + *state = 0; + return LEX_FALSE; + break; + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + case 14: /* null: 1 */ + { + switch (*(*p)++) + { + case 'u': + *state = 15; + break; + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + case 15: /* null: 2 */ + { + switch (*(*p)++) + { + case 'l': + *state = 16; + break; + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + case 16: /* null: 3 */ + { + switch (*(*p)++) + { + case 'l': + *state = 0; + return LEX_NULL; + break; + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + case 17: /* number: minus sign */ + { + switch (**p) + { + case '0': + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + ++*p; + *state = 18; /* number: '0' */ + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + ++*p; + *state = 19; /* number: decimal followup */ + break; + + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + case 18: /* number: '0' */ + { + switch (**p) + { + case '\x20': /* space */ + case '\x09': /* horizontal tab */ + case '\x0A': /* line feed or new line */ + case '\x0D': /* Carriage return */ + ++*p; + case ']': + case '}': + case ',': + *state = 0; + return LEX_NUMBER; + break; + + case '.': + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + ++*p; + *state = 20; /* number: frac start */ + break; + + case 'e': + case 'E': + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + ++*p; + *state = 22; /* number: exp start */ + break; + + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + case 19: /* number: int followup */ + { + switch (**p) + { + case '\x20': /* space */ + case '\x09': /* horizontal tab */ + case '\x0A': /* line feed or new line */ + case '\x0D': /* Carriage return */ + ++*p; + case ']': + case '}': + case ',': + *state = 0; + return LEX_NUMBER; + break; + + case '.': + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + ++*p; + *state = 20; /* number: frac start */ + break; + + case 'e': + case 'E': + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + ++*p; + *state = 22; /* number: exp start */ + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + ++*p; + break; + + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + case 20: /* number: frac start */ + { + switch (**p) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + ++*p; + *state = 21; /* number: frac continue */ + break; + + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + case 21: /* number: frac continue */ + { + switch (**p) + { + case '\x20': /* space */ + case '\x09': /* horizontal tab */ + case '\x0A': /* line feed or new line */ + case '\x0D': /* Carriage return */ + ++*p; + case ']': + case '}': + case ',': + *state = 0; + return LEX_NUMBER; + break; + + case 'e': + case 'E': + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + ++*p; + *state = 22; /* number: exp start */ + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + ++*p; + break; + + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + case 22: /* number: exp start */ + { + switch (**p) + { + case '-': + case '+': + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + ++*p; + *state = 23; /* number: exp continue */ + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + ++*p; + *state = 24; /* number: exp end */ + break; + + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + case 23: /* number: exp continue */ + { + switch (**p) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + ++*p; + *state = 24; /* number: exp end */ + break; + + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + case 24: /* number: exp end */ + { + switch (**p) + { + case '\x20': /* space */ + case '\x09': /* horizontal tab */ + case '\x0A': /* line feed or new line */ + case '\x0D': /* Carriage return */ + ++*p; + case ']': + case '}': + case ',': + *state = 0; + return LEX_NUMBER; + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (rcs_catc (*text, **p) != RS_OK) + return LEX_MEMORY; + ++*p; + break; + + default: + return LEX_INVALID_CHARACTER; + break; + } + } + break; + + default: + printf ("*state missing: %d\n", *state); + return LEX_INVALID_CHARACTER; + } + + } + + *p = NULL; + return LEX_MORE; +} + + +enum json_error +json_parse_fragment (struct json_parsing_info *info, const char *buffer) +{ + json_t *temp = NULL; + + info->p = buffer; + while (*info->p != '\0') + { + switch (info->state) + { + case 0: /* starting point */ + { + switch (lexer (buffer, &info->p, &info->lex_state, & info->lex_text)) + { + case LEX_BEGIN_OBJECT: + info->state = 1; /* begin object */ + break; + + case LEX_BEGIN_ARRAY: + info->state = 7; /* begin array */ + break; + + case LEX_INVALID_CHARACTER: + return JSON_MALFORMED_DOCUMENT; + break; + + default: + printf ("state %d: defaulted\n", info->state); + return JSON_MALFORMED_DOCUMENT; + break; + } + } + break; + + case 1: /* open object */ + { + if (info->cursor == NULL) + { + if ((info->cursor = json_new_object ()) == NULL) + { + return JSON_MEMORY; + } + } + else + { + if ((temp = json_new_object ()) == NULL) + { + return JSON_MEMORY; + } + if (json_insert_child (info->cursor, temp) != JSON_OK) + { + return JSON_UNKNOWN_PROBLEM; + } + info->cursor = temp; + temp = NULL; + } + info->state = 2; /* just entered an object */ + } + break; + + case 2: /* opened object */ + { + switch (lexer (buffer, &info->p, &info->lex_state, & info->lex_text)) + { + case LEX_STRING: + if ((temp = json_new_value (JSON_STRING)) == NULL) + return JSON_MEMORY; + temp->text = rcs_unwrap (info->lex_text), info->lex_text = NULL; + if (json_insert_child (info->cursor, temp) != JSON_OK) + { + /*TODO return value according to the value returned from json_insert_child() */ + return JSON_UNKNOWN_PROBLEM; + } + info->cursor = temp; + temp = NULL; + info->state = 5; /* label, pre label:value separator */ + break; + + case LEX_END_OBJECT: + if (info->cursor->parent == NULL) + { + info->state = 99; /* finished document. only accept whitespaces until EOF */ + } + else + { + info->cursor = info->cursor->parent; + switch (info->cursor->type) + { + case JSON_STRING: + info->cursor = info->cursor->parent; + if (info->cursor->type != JSON_OBJECT) + { + return JSON_BAD_TREE_STRUCTURE; + } + else + { + info->state = 3; /* finished adding a field to an object */ + } + break; + + case JSON_ARRAY: + info->state = 9; + break; + + default: + return JSON_BAD_TREE_STRUCTURE; + } + } + break; + + case LEX_MORE: + return JSON_INCOMPLETE_DOCUMENT; + break; + + default: + printf ("state %d: defaulted\n", info->state); + return JSON_MALFORMED_DOCUMENT; + break; + } + } + break; + + case 3: /* finished adding a field to an object */ + { + switch (lexer (buffer, &info->p, &info->lex_state, & info->lex_text)) + { + case LEX_VALUE_SEPARATOR: + info->state = 4; /* sibling, post-object */ + break; + + case LEX_END_OBJECT: + if (info->cursor->parent == NULL) + { + info->state = 99; /* parse until EOF */ + } + else + { + info->cursor = info->cursor->parent; + switch (info->cursor->type) + { + case JSON_STRING: + info->cursor = info->cursor->parent; + if (info->cursor->type != JSON_OBJECT) + { + return JSON_BAD_TREE_STRUCTURE; + } + else + { + info->state = 3; /* finished adding a field to an object */ + } + break; + + case JSON_ARRAY: + info->state = 9; + break; + + default: + return JSON_BAD_TREE_STRUCTURE; + } + } + break; + + case LEX_MORE: + return JSON_INCOMPLETE_DOCUMENT; + break; + + default: + printf ("state %d: defaulted\n", info->state); + return JSON_MALFORMED_DOCUMENT; + break; + } + } + break; + + case 4: /* sibling, post-object */ + { + switch (lexer (buffer, &info->p, &info->lex_state, & info->lex_text)) + { + case LEX_STRING: + if ((temp = json_new_value (JSON_STRING)) == NULL) + return JSON_MEMORY; + temp->text = rcs_unwrap (info->lex_text), info->lex_text = NULL; + if (json_insert_child (info->cursor, temp) != JSON_OK) + { + return JSON_UNKNOWN_PROBLEM; + } + info->cursor = temp; + temp = NULL; + info->state = 5; + break; + + case LEX_MORE: + return JSON_INCOMPLETE_DOCUMENT; + break; + + case LEX_INVALID_CHARACTER: + return JSON_ILLEGAL_CHARACTER; + break; + + default: + printf ("state %d: defaulted\n", info->state); + return JSON_MALFORMED_DOCUMENT; + break; + } + } + break; + + case 5: /* label, pre name separator */ + { + switch (lexer (buffer, &info->p, &info->lex_state, & info->lex_text)) + { + case LEX_NAME_SEPARATOR: + info->state = 6; /* label, pos label:value separator */ + break; + + case LEX_MORE: + return JSON_INCOMPLETE_DOCUMENT; + break; + + default: + printf ("state %d: defaulted\n", info->state); + return JSON_MALFORMED_DOCUMENT; + break; + } + } + break; + + case 6: /* label, pos name separator */ + { + unsigned int value; /* to avoid redundant code */ + + switch (value = lexer (buffer, &info->p, &info->lex_state, & info->lex_text)) + { + case LEX_STRING: + if ((temp = json_new_value (JSON_STRING)) == NULL) + return JSON_MEMORY; + temp->text = rcs_unwrap (info->lex_text), info->lex_text = NULL; + if (json_insert_child (info->cursor, temp) != JSON_OK) + { + /*TODO specify the exact error message */ + return JSON_UNKNOWN_PROBLEM; + } + if (info->cursor->parent == NULL) + { + info->state = 99; /* finished document. only accepts whitespaces until EOF */ + } + else + { + info->cursor = info->cursor->parent; + } + temp = NULL; + info->state = 3; /* finished adding a field to an object */ + break; + + case LEX_NUMBER: + if ((temp = json_new_value (JSON_NUMBER)) == NULL) + return JSON_MEMORY; + temp->text = rcs_unwrap (info->lex_text), info->lex_text = NULL; + if (json_insert_child (info->cursor, temp) != JSON_OK) + { + /*TODO specify the exact error message */ + return JSON_UNKNOWN_PROBLEM; + } + if (info->cursor->parent == NULL) + { + info->state = 99; /* finished document. only accepts whitespaces until EOF */ + } + else + { + info->cursor = info->cursor->parent; + } + temp = NULL; + info->state = 3; /* finished adding a field to an object */ + break; + + case LEX_TRUE: + if ((temp = json_new_value (JSON_TRUE)) == NULL) + return JSON_MEMORY; + if (json_insert_child (info->cursor, temp) != JSON_OK) + { + /*TODO specify the exact error message */ + return JSON_UNKNOWN_PROBLEM; + } + if (info->cursor->parent == NULL) + { + info->state = 99; /* finished document. only accepts whitespaces until EOF */ + } + else + { + info->cursor = info->cursor->parent; + } + temp = NULL; + info->state = 3; /* finished adding a field to an object */ + break; + + case LEX_FALSE: + if ((temp = json_new_value (JSON_FALSE)) == NULL) + return JSON_MEMORY; + if (json_insert_child (info->cursor, temp) != JSON_OK) + { + /*TODO specify the exact error message */ + return JSON_UNKNOWN_PROBLEM; + } + if (info->cursor->parent == NULL) + { + info->state = 99; /* finished document. only accepts whitespaces until EOF */ + } + else + { + info->cursor = info->cursor->parent; + } + temp = NULL; + info->state = 3; /* finished adding a field to an object */ + break; + + case LEX_NULL: + if ((temp = json_new_value (JSON_NULL)) == NULL) + return JSON_MEMORY; + if (json_insert_child (info->cursor, temp) != JSON_OK) + { + /*TODO specify the exact error message */ + return JSON_UNKNOWN_PROBLEM; + } + if (info->cursor->parent == NULL) + { + info->state = 99; /* finished document. only accepts whitespaces until EOF */ + } + else + { + info->cursor = info->cursor->parent; + } + temp = NULL; + info->state = 3; /* finished adding a field to an object */ + break; + + case LEX_BEGIN_OBJECT: + info->state = 1; + break; + + case LEX_BEGIN_ARRAY: + info->state = 7; + break; + + case LEX_MORE: + return JSON_INCOMPLETE_DOCUMENT; + break; + + case LEX_MEMORY: + return JSON_MEMORY; + break; + + case LEX_INVALID_CHARACTER: + return JSON_ILLEGAL_CHARACTER; + break; + + default: + printf ("state %d: defaulted\n", info->state); + return JSON_MALFORMED_DOCUMENT; + break; + } + } + break; + + case 7: /* open array */ + { + if (info->cursor == NULL) + { + if ((info->cursor = json_new_array ()) == NULL) + { + return JSON_MEMORY; + } + } + else + { + if ((temp = json_new_array ()) == NULL) + { + return JSON_MEMORY; + } + if (json_insert_child (info->cursor, temp) != JSON_OK) + { + return JSON_UNKNOWN_PROBLEM; + } + info->cursor = temp; + temp = NULL; + } + info->state = 8; /* just entered an array */ + } + break; + + case 8: /* just entered an array */ + { + switch (lexer (buffer, &info->p, &info->lex_state, & info->lex_text)) + { + case LEX_STRING: + if ((temp = json_new_value (JSON_STRING)) == NULL) + return JSON_MEMORY; + temp->text = rcs_unwrap (info->lex_text), info->lex_text = NULL; + if (json_insert_child (info->cursor, temp) != JSON_OK) + { + return JSON_UNKNOWN_PROBLEM; + } + temp = NULL; + info->state = 9; /* label, pre label:value separator */ + break; + + case LEX_NUMBER: + if ((temp = json_new_value (JSON_NUMBER)) == NULL) + return JSON_MEMORY; + temp->text = rcs_unwrap (info->lex_text), info->lex_text = NULL; + if (json_insert_child (info->cursor, temp) != JSON_OK) + { + return JSON_UNKNOWN_PROBLEM; + } + temp = NULL; + info->state = 9; /* label, pre label:value separator */ + break; + + case LEX_TRUE: + if ((temp = json_new_value (JSON_TRUE)) == NULL) + return JSON_MEMORY; + if (json_insert_child (info->cursor, temp) != JSON_OK) + { + return JSON_UNKNOWN_PROBLEM; + } + info->state = 9; /* label, pre label:value separator */ + break; + + case LEX_FALSE: + if ((temp = json_new_value (JSON_FALSE)) == NULL) + return JSON_MEMORY; + if (json_insert_child (info->cursor, temp) != JSON_OK) + { + return JSON_UNKNOWN_PROBLEM; + } + info->state = 9; /* label, pre label:value separator */ + break; + + case LEX_NULL: + if ((temp = json_new_value (JSON_NULL)) == NULL) + return JSON_MEMORY; + if (json_insert_child (info->cursor, temp) != JSON_OK) + { + return JSON_UNKNOWN_PROBLEM; + } + info->state = 9; /* label, pre label:value separator */ + break; + + case LEX_BEGIN_ARRAY: + info->state = 7; /* open array */ + break; + + case LEX_END_ARRAY: + if (info->cursor->parent == NULL) + { + /*TODO implement this */ + info->state = 99; /* finished document. only accept whitespaces until EOF */ + } + else + { + info->cursor = info->cursor->parent; + switch (info->cursor->type) + { + case JSON_STRING: + if (info->cursor->parent == NULL) + return JSON_BAD_TREE_STRUCTURE; + else + { + info->cursor = info->cursor->parent; + if (info->cursor->type != JSON_OBJECT) + { + return JSON_BAD_TREE_STRUCTURE; + } + + info->state = 3; /* followup to adding child to array */ + } + break; + + case JSON_ARRAY: + info->state = 9; /* followup to adding child to array */ + break; + + default: + return JSON_BAD_TREE_STRUCTURE; + } + } + break; + + case LEX_BEGIN_OBJECT: + info->state = 1; /* open object */ + break; + + case LEX_MORE: + return JSON_INCOMPLETE_DOCUMENT; + break; + + case LEX_INVALID_CHARACTER: + return JSON_ILLEGAL_CHARACTER; + break; + + default: + printf ("state %d: defaulted\n", info->state); + return JSON_MALFORMED_DOCUMENT; + break; + } + } + break; + + case 9: /* followup to adding child to array */ + { + switch (lexer (buffer, &info->p, &info->lex_state, & info->lex_text)) + { + case LEX_VALUE_SEPARATOR: + info->state = 8; + break; + + case LEX_END_ARRAY: + if (info->cursor->parent == NULL) + { + info->state = 99; /* finished document. only accept whitespaces until EOF */ + } + else + { + info->cursor = info->cursor->parent; + switch (info->cursor->type) + { + case JSON_STRING: + if (info->cursor->parent == NULL) + { + info->state = 99; /* finished document. only accept whitespaces until EOF */ + } + else + { + info->cursor = info->cursor->parent; + if (info->cursor->type != JSON_OBJECT) + { + return JSON_BAD_TREE_STRUCTURE; + } + else + { + info->state = 3; /* followup to adding child to array */ + } + } + break; + + case JSON_ARRAY: + info->state = 9; /* followup to adding child to array */ + break; + + default: + return JSON_BAD_TREE_STRUCTURE; + } + } + break; + + case LEX_MORE: + return JSON_INCOMPLETE_DOCUMENT; + break; + + default: + printf ("state %d: defaulted\n", info->state); + return JSON_MALFORMED_DOCUMENT; + break; + } + } + break; + + case 99: /* finished document. only accept whitespaces until EOF */ + { + switch (lexer (buffer, &info->p, &info->lex_state, & info->lex_text)) + { + case LEX_MORE: + return JSON_WAITING_FOR_EOF; + break; + + case LEX_MEMORY: + return JSON_MEMORY; + break; + + default: + return JSON_MALFORMED_DOCUMENT; + break; + } + } + break; + + default: + printf ("invalid parser state %d: defaulted\n", info->state); + return JSON_UNKNOWN_PROBLEM; + } + } + info->p = NULL; + if (info->state == 99) + return JSON_WAITING_FOR_EOF; + else + return JSON_INCOMPLETE_DOCUMENT; +} + + + +enum json_error +json_parse_document (json_t ** root, const char *text) +{ + enum json_error error; + struct json_parsing_info *jpi; + + /* initialize the parsing structure */ + if (VIR_ALLOC(jpi) < 0) + { + return JSON_MEMORY; + } + json_jpi_init (jpi); + + error = json_parse_fragment (jpi, text); + if ((error == JSON_WAITING_FOR_EOF) || (error == JSON_OK)) + { + *root = jpi->cursor; + VIR_FREE(jpi); + return JSON_OK; + } + else + { + VIR_FREE(jpi); + return error; + } +} + + +enum json_error +json_saxy_parse (struct json_saxy_parser_status *jsps, struct json_saxy_functions *jsf, char c) +{ + /*TODO handle a string instead of a single char */ + /* temp variables */ + rcstring *temp; + + temp = NULL; + + /* goto where we left off */ + switch (jsps->state) + { + case 0: /* general state. everything goes. */ + goto state0; + break; + case 1: /* parse string */ + goto state1; + break; + case 2: /* parse string: escaped character */ + goto state2; + break; + case 3: /* parse string: escaped unicode 1 */ + goto state3; + break; + case 4: /* parse string: escaped unicode 2 */ + goto state4; + break; + case 5: /* parse string: escaped unicode 3 */ + goto state5; + break; + case 6: /* parse string: escaped unicode 4 */ + goto state6; + break; + case 7: /* parse true: tr */ + goto state7; + break; + case 8: /* parse true: tru */ + goto state8; + break; + case 9: /* parse true: true */ + goto state9; + break; + case 10: /* parse false: fa */ + goto state10; + break; + case 11: /* parse false: fal */ + goto state11; + break; + case 12: /* parse false: fals */ + goto state12; + break; + case 13: /* parse false: false */ + goto state13; + break; + case 14: /* parse null: nu */ + goto state14; + break; + case 15: /* parse null: nul */ + goto state15; + break; + case 16: /* parse null: null */ + goto state16; + break; + case 17: /* parse number: 0 */ + goto state17; + break; + case 18: /* parse number: start fraccional part */ + goto state18; + break; + case 19: /* parse number: fraccional part */ + goto state19; + break; + case 20: /* parse number: start exponent part */ + goto state20; + break; + case 21: /* parse number: exponent part */ + goto state21; + break; + case 22: /* parse number: exponent sign part */ + goto state22; + break; + case 23: /* parse number: start negative */ + goto state23; + break; + case 24: /* parse number: decimal part */ + goto state24; + break; + case 25: /* open object */ + goto state25; + break; + case 26: /* close object/array */ + goto state26; + break; + case 27: /* sibling followup */ + goto state27; + break; + + default: /* oops... this should never be reached */ + return JSON_UNKNOWN_PROBLEM; + } + +state0: /* starting point */ + { + switch (c) + { + case '\x20': + case '\x09': + case '\x0A': + case '\x0D': /* JSON insignificant white spaces */ + break; + + case '\"': /* starting a string */ + jsps->string_length_limit_reached = 0; + jsps->state = 1; + break; + + case '{': + if (jsf->open_object != NULL) + jsf->open_object (); + jsps->state = 25; /*open object */ + break; + + case '}': + if (jsf->close_object != NULL) + jsf->close_object (); + jsps->state = 26; /* close object/array */ + break; + + case '[': + if (jsf->open_array != NULL) + jsf->open_array (); +/* jsps->state = 0; // redundant*/ + break; + + case ']': + if (jsf->close_array != NULL) + jsf->close_array (); + jsps->state = 26; /* close object/array */ + break; + + case 't': + jsps->state = 7; /* parse true: tr */ + break; + + case 'f': + jsps->state = 10; /* parse false: fa */ + break; + + case 'n': + jsps->state = 14; /* parse null: nu */ + break; + + case ':': + if (jsf->label_value_separator != NULL) + jsf->label_value_separator (); +/* jsps->state = 0; // redundant*/ + break; + + case ',': + if (jsf->sibling_separator != NULL) + jsf->sibling_separator (); + jsps->state = 27; /* sibling followup */ + break; + + case '0': + jsps->string_length_limit_reached = 0; + jsps->state = 17; /* parse number: 0 */ + if ((jsps->temp = rcs_create (5)) == NULL) + { + return JSON_MEMORY; + } + if (rcs_catc ((jsps->temp), '0') != RS_OK) + { + return JSON_MEMORY; + } + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + jsps->string_length_limit_reached = 0; + jsps->state = 24; /* parse number: decimal */ + if ((jsps->temp = rcs_create (5)) == NULL) + { + return JSON_MEMORY; + } + if (rcs_catc ((jsps->temp), c) != RS_OK) + { + return JSON_MEMORY; + } + break; + + case '-': + jsps->string_length_limit_reached = 0; + jsps->state = 23; /* number: */ + jsps->temp = NULL; + if ((jsps->temp = rcs_create (5)) == NULL) + { + return JSON_MEMORY; + } + if (rcs_catc ((jsps->temp), '-') != RS_OK) + { + return JSON_MEMORY; + } + + break; + + default: + return JSON_ILLEGAL_CHARACTER; + break; + } + return JSON_OK; + } + +state1: /* parse string */ + { + switch (c) + { + case '\\': + if (!jsps->string_length_limit_reached) + { + if (rcs_length ((jsps->temp)) < JSON_MAX_STRING_LENGTH - 1) /* check if there is space for a two character escape sequence */ + { + if (rcs_catc ((jsps->temp), '\\') != RS_OK) + { + return JSON_MEMORY; + } + } + else + { + jsps->string_length_limit_reached = 1; + } + } + jsps->state = 2; /* parse string: escaped character */ + break; + + case '\"': /* end of string */ + if ((jsps->temp) != NULL) + { + jsps->state = 0; /* starting point */ + if (jsf->new_string != NULL) + jsf->new_string (((jsps->temp))->text); /*copied or integral? */ + rcs_free (& jsps->temp); + } + else + return JSON_UNKNOWN_PROBLEM; + break; + + default: + if (!jsps->string_length_limit_reached) + { + if (rcs_length ((jsps->temp)) < JSON_MAX_STRING_LENGTH) /* check if there is space for a two character escape sequence */ + { + if (rcs_catc ((jsps->temp), c) != RS_OK) + { + return JSON_MEMORY; + } + } + else + { + jsps->string_length_limit_reached = 1; + } + } + break; + } + return JSON_OK; + } + +state2: /* parse string: escaped character */ + { + switch (c) + { + case '\"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + if (!jsps->string_length_limit_reached) + { + if (rcs_length ((jsps->temp)) < JSON_MAX_STRING_LENGTH) + { + if (rcs_catc ((jsps->temp), c) != RS_OK) + { + return JSON_MEMORY; + } + } + else + { + jsps->string_length_limit_reached = 1; + } + } + break; + + case 'u': + if (!jsps->string_length_limit_reached) + { + if (rcs_length ((jsps->temp)) < JSON_MAX_STRING_LENGTH - 4) + { + if (rcs_catc ((jsps->temp), 'u') != RS_OK) + { + return JSON_MEMORY; + } + } + else + { + jsps->string_length_limit_reached = 1; + } + } + jsps->state = 3; /* parse string: escaped unicode 1; */ + break; + + default: + return JSON_ILLEGAL_CHARACTER; + break; + } + return JSON_OK; + } + +state3: /* parse string: escaped unicode 1 */ + { + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + if (!jsps->string_length_limit_reached) + { + if (rcs_length ((jsps->temp)) < JSON_MAX_STRING_LENGTH - 3) + { + if (rcs_catc ((jsps->temp), 'u') != RS_OK) + { + return JSON_MEMORY; + } + } + else + { + jsps->string_length_limit_reached = 1; + } + } + jsps->state = 4; /* parse string. escaped unicode 2 */ + break; + + default: + return JSON_ILLEGAL_CHARACTER; + } + return JSON_OK; + } + +state4: /* parse string: escaped unicode 2 */ + { + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + if (!jsps->string_length_limit_reached) + { + if (rcs_length ((jsps->temp)) < JSON_MAX_STRING_LENGTH - 2) + { + if (rcs_catc ((jsps->temp), c) != RS_OK) + { + return JSON_MEMORY; + } + } + else + { + jsps->string_length_limit_reached = 1; + } + } + jsps->state = 5; /* parse string. escaped unicode 3 */ + break; + + default: + return JSON_ILLEGAL_CHARACTER; + } + return JSON_OK; + } + +state5: /* parse string: escaped unicode 3 */ + { + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + if (!jsps->string_length_limit_reached) + { + if (rcs_length ((jsps->temp)) < JSON_MAX_STRING_LENGTH - 1) + { + if (rcs_catc ((jsps->temp), c) != RS_OK) + { + return JSON_MEMORY; + } + } + else + { + jsps->string_length_limit_reached = 1; + } + } + jsps->state = 6; /* parse string. escaped unicode 4 */ + break; + + default: + return JSON_ILLEGAL_CHARACTER; + } + return JSON_OK; + } + +state6: /* parse string: escaped unicode 4 */ + { + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + if (!jsps->string_length_limit_reached) + { + if (rcs_length ((jsps->temp)) < JSON_MAX_STRING_LENGTH) + { + if (rcs_catc ((jsps->temp), c) != RS_OK) + { + return JSON_MEMORY; + } + } + else + { + jsps->string_length_limit_reached = 1; + } + } + jsps->state = 1; /* parse string */ + break; + + default: + return JSON_ILLEGAL_CHARACTER; + } + return JSON_OK; + } + +state7: /* parse true: tr */ + { + if (c != 'r') + { + return JSON_ILLEGAL_CHARACTER; + } + + jsps->state = 8; /* parse true: tru */ + return JSON_OK; + } + +state8: /* parse true: tru */ + { + if (c != 'u') + { + return JSON_ILLEGAL_CHARACTER; + } + + jsps->state = 9; /* parse true: true */ + return JSON_OK; + } + +state9: /* parse true: true */ + { + if (c != 'e') + { + return JSON_ILLEGAL_CHARACTER; + } + + jsps->state = 0; /* back to general state. */ + if (jsf->new_true != NULL) + jsf->new_true (); + return JSON_OK; + } + +state10: /* parse false: fa */ + { + if (c != 'a') + { + return JSON_ILLEGAL_CHARACTER; + } + + jsps->state = 11; /* parse true: fal */ + return JSON_OK; + } + +state11: /* parse false: fal */ + { + if (c != 'l') + { + return JSON_ILLEGAL_CHARACTER; + } + + jsps->state = 12; /* parse true: fals */ + return JSON_OK; + } + +state12: /* parse false: fals */ + { + if (c != 's') + { + return JSON_ILLEGAL_CHARACTER; + } + + jsps->state = 13; /* parse true: false */ + return JSON_OK; + } + +state13: /* parse false: false */ + { + if (c != 'e') + { + return JSON_ILLEGAL_CHARACTER; + } + + jsps->state = 0; /* general state. everything goes. */ + if (jsf->new_false != NULL) + jsf->new_false (); + return JSON_OK; + } + +state14: /* parse null: nu */ + { + if (c != 'u') + { + return JSON_ILLEGAL_CHARACTER; + } + + jsps->state = 15; /* parse null: nul */ + return JSON_OK; + } + +state15: /* parse null: nul */ + { + if (c != 'l') + { + return JSON_ILLEGAL_CHARACTER; + } + + jsps->state = 16; /* parse null: null */ + return JSON_OK; + } + +state16: /* parse null: null */ + { + if (c != 'l') + { + return JSON_ILLEGAL_CHARACTER; + } + + jsps->state = 0; /* general state. everything goes. */ + if (jsf->new_null != NULL) + jsf->new_null (); + return JSON_OK; + } + +state17: /* parse number: 0 */ + { + switch (c) + { + case '.': + if ((jsps->temp = rcs_create (5)) == NULL) + { + return JSON_MEMORY; + } + if (rcs_catc ((jsps->temp), '.') != RS_OK) + { + return JSON_MEMORY; + } + jsps->state = 18; /* parse number: fraccional part */ + break; + + case '\x20': + case '\x09': + case '\x0A': + case '\x0D': /* JSON insignificant white spaces */ + if ((jsps->temp) == NULL) + return JSON_MEMORY; + if (jsf->new_number != NULL) + { + jsf->new_number ((jsps->temp)->text); + } + rcs_free (& jsps->temp); + + jsps->state = 0; + break; + + case '}': + if ((jsps->temp) == NULL) + return JSON_MEMORY; + if (jsf->new_number != NULL) + { + jsf->new_number ((jsps->temp)->text); + } + rcs_free (& jsps->temp); + + if (jsf->open_object != NULL) + jsf->close_object (); + jsps->state = 26; /* close object/array */ + break; + + case ']': + + if ((jsps->temp) == NULL) + return JSON_MEMORY; + if (jsf->new_number != NULL) + { + jsf->new_number ((jsps->temp)->text); + } + rcs_free (& jsps->temp); + + if (jsf->open_object != NULL) + jsf->close_array (); + jsps->state = 26; /* close object/array */ + break; + + case ',': + + if ((jsps->temp) == NULL) + return JSON_MEMORY; + if (jsf->new_number != NULL) + { + jsf->new_number ((jsps->temp)->text); + } + rcs_free (& jsps->temp); + + if (jsf->open_object != NULL) + jsf->label_value_separator (); + jsps->state = 27; /* sibling followup */ + break; + + default: + return JSON_ILLEGAL_CHARACTER; + break; + } + + return JSON_OK; + } + +state18: /* parse number: start fraccional part */ + { + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (!jsps->string_length_limit_reached) + { + if (rcs_length ((jsps->temp)) < JSON_MAX_STRING_LENGTH / 2) + { + if (rcs_catc ((jsps->temp), c) != RS_OK) + { + return JSON_MEMORY; + } + } + else + { + jsps->string_length_limit_reached = 1; + } + } + jsps->state = 19; /* parse number: fractional part */ + break; + + default: + return JSON_ILLEGAL_CHARACTER; + break; + } + return JSON_OK; + } + +state19: /* parse number: fraccional part */ + { + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (!jsps->string_length_limit_reached) + { + if (rcs_length ((jsps->temp)) < JSON_MAX_STRING_LENGTH / 2) + { + if (rcs_catc ((jsps->temp), c) != RS_OK) + { + return JSON_MEMORY; + } + } + else + { + jsps->string_length_limit_reached = 1; + } + } +/* jsps->state = 19; // parse number: fractional part*/ + break; + + case 'e': + case 'E': + if (rcs_catc ((jsps->temp), c) != RS_OK) + { + return JSON_MEMORY; + } + + jsps->state = 20; /* parse number: start exponent part */ + break; + + + case '\x20': + case '\x09': + case '\x0A': + case '\x0D': /* JSON insignificant white spaces */ + + if ((jsps->temp) == NULL) + return JSON_MEMORY; + if (jsf->new_number != NULL) + { + jsf->new_number ((jsps->temp)->text); + } + rcs_free (& jsps->temp); + + jsps->state = 0; + break; + + case '}': + + if ((jsps->temp) == NULL) + return JSON_MEMORY; + if (jsf->new_number != NULL) + { + jsf->new_number ((jsps->temp)->text); + } + rcs_free (& jsps->temp); + + if (jsf->open_object != NULL) + jsf->close_object (); + jsps->state = 26; /* close object/array */ + break; + + case ']': + if (jsf->new_number != NULL) + { + if ((jsps->temp) == NULL) + return JSON_MEMORY; + jsf->new_number ((jsps->temp)->text); + rcs_free (& jsps->temp); + } + else + { + rcs_free (& jsps->temp); + jsps->temp = NULL; + } + if (jsf->open_object != NULL) + jsf->close_array (); + jsps->state = 26; /* close object/array */ + break; + + case ',': + + if ((jsps->temp) == NULL) + return JSON_MEMORY; + if (jsf->new_number != NULL) + { + jsf->new_number ((jsps->temp)->text); + } + rcs_free (& jsps->temp); + + if (jsf->label_value_separator != NULL) + jsf->label_value_separator (); + jsps->state = 27; /* sibling followup */ + break; + + + default: + return JSON_ILLEGAL_CHARACTER; + break; + } + return JSON_OK; + } + +state20: /* parse number: start exponent part */ + { + switch (c) + { + case '+': + case '-': + jsps->string_length_limit_reached = 0; + if (rcs_catc ((jsps->temp), c) != RS_OK) + { + return JSON_MEMORY; + } + + jsps->state = 22; /* parse number: exponent sign part */ + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (!jsps->string_length_limit_reached) + { + if (rcs_length ((jsps->temp)) < JSON_MAX_STRING_LENGTH) + { + if (rcs_catc ((jsps->temp), c) != RS_OK) + { + return JSON_MEMORY; + } + + } + else + { + jsps->string_length_limit_reached = 1; + } + } + jsps->state = 21; /* parse number: exponent part */ + break; + + default: + return JSON_ILLEGAL_CHARACTER; + break; + } + return JSON_OK; + } + +state21: /* parse number: exponent part */ + { + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (!jsps->string_length_limit_reached) + { + if (rcs_length ((jsps->temp)) < JSON_MAX_STRING_LENGTH) + { + if (rcs_catc ((jsps->temp), c) != RS_OK) + { + return JSON_MEMORY; + } + } + else + { + jsps->string_length_limit_reached = 1; + } + } +/* jsps->state = 21; // parse number: exponent part*/ + break; + + case '\x20': + case '\x09': + case '\x0A': + case '\x0D': /* JSON insignificant white spaces */ + + if ((jsps->temp) == NULL) + return JSON_MEMORY; + if (jsf->new_number != NULL) + { + jsf->new_number ((jsps->temp)->text); + } + rcs_free (& jsps->temp); + + jsps->state = 0; + break; + + case '}': + if ((jsps->temp) == NULL) + return JSON_MEMORY; + if (jsf->new_number != NULL) + { + jsf->new_number ((jsps->temp)->text); + } + rcs_free (& jsps->temp); + + if (jsf->open_object != NULL) + jsf->close_object (); + jsps->state = 26; /* close object */ + break; + + case ']': + if (jsf->new_number != NULL) + { + if ((jsps->temp) == NULL) + return JSON_MEMORY; + jsf->new_number ((jsps->temp)->text); + free (jsps->temp); + jsps->temp = NULL; + } + else + { + free ((jsps->temp)); + jsps->temp = NULL; + } + if (jsf->open_object != NULL) + jsf->close_array (); + jsps->state = 26; /* close object/array */ + break; + + case ',': + if (jsf->new_number != NULL) + { + if ((jsps->temp) == NULL) + return JSON_MEMORY; + jsf->new_number ((jsps->temp)->text); + free ((jsps->temp)); + jsps->temp = NULL; + } + else + { + free (jsps->temp); + jsps->temp = NULL; + } + if (jsf->label_value_separator != NULL) + jsf->label_value_separator (); + jsps->state = 27; /* sibling followup */ + break; + + default: + return JSON_ILLEGAL_CHARACTER; + break; + } + return JSON_OK; + } + +state22: /* parse number: start exponent part */ + { + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (!jsps->string_length_limit_reached) + { + if (rcs_length ((jsps->temp)) < JSON_MAX_STRING_LENGTH) + { + rcs_catc ((jsps->temp), c); + } + else + { + jsps->string_length_limit_reached = 1; + } + } + jsps->state = 21; /* parse number: exponent part */ + break; + + default: + return JSON_ILLEGAL_CHARACTER; + break; + } + return JSON_OK; + } + +state23: /* parse number: start negative */ + { + switch (c) + { + case '0': + rcs_catc ((jsps->temp), c); + jsps->state = 17; /* parse number: 0 */ + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (!jsps->string_length_limit_reached) + { + if (rcs_length ((jsps->temp)) < JSON_MAX_STRING_LENGTH / 2) + { + if ((jsps->temp = rcs_create (5)) == NULL) + { + return JSON_MEMORY; + } + if (rcs_catc ((jsps->temp), c) != RS_OK) + { + return JSON_MEMORY; + } + else + { + jsps->string_length_limit_reached = 1; + } + } + } + jsps->state = 24; /* parse number: start decimal part */ + break; + + default: + return JSON_ILLEGAL_CHARACTER; + break; + } + return JSON_OK; + } + +state24: /* parse number: decimal part */ + { + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (!jsps->string_length_limit_reached) + { + if (rcs_length ((jsps->temp)) < JSON_MAX_STRING_LENGTH / 2) + { + if ((jsps->temp = rcs_create (5)) == NULL) + { + return JSON_MEMORY; + } + if (rcs_catc ((jsps->temp), c) != RS_OK) + { + return JSON_MEMORY; + } + } + else + { + jsps->string_length_limit_reached = 1; + } + } +/* jsps->state = 24; // parse number: decimal part*/ + break; + + case '.': + if ((jsps->temp = rcs_create (5)) == NULL) + { + return JSON_MEMORY; + } + if (rcs_catc ((jsps->temp), '.') != RS_OK) + { + return JSON_MEMORY; + } + + jsps->state = 18; /* parse number: start exponent part */ + break; + + case 'e': + case 'E': + if ((jsps->temp = rcs_create (5)) == NULL) + { + return JSON_MEMORY; + } + if (rcs_catc ((jsps->temp), c) != RS_OK) + { + return JSON_MEMORY; + } + + jsps->string_length_limit_reached = 0; /* reset to accept the exponential part */ + jsps->state = 20; /* parse number: start exponent part */ + break; + + case '\x20': + case '\x09': + case '\x0A': + case '\x0D': /* JSON insignificant white spaces */ + if ((jsps->temp) == NULL) + return JSON_MEMORY; + if (jsf->new_number != NULL) + { + jsf->new_number ((jsps->temp)->text); + } + rcs_free (& jsps->temp); + + jsps->state = 0; + break; + + case '}': + if ((jsps->temp) == NULL) + return JSON_MEMORY; + if (jsf->new_number != NULL) + { + jsf->new_number ((jsps->temp)->text); + } + rcs_free (& jsps->temp); + + if (jsf->open_object != NULL) + jsf->close_object (); + jsps->state = 26; /* close object/array */ + break; + + case ']': + if ((jsps->temp) == NULL) + return JSON_MEMORY; + if (jsf->new_number != NULL) + { + jsf->new_number ((jsps->temp)->text); + } + rcs_free (& jsps->temp); + + if (jsf->open_object != NULL) + jsf->close_array (); + jsps->state = 26; /* close object/array */ + break; + + case ',': + if ((jsps->temp) == NULL) + return JSON_MEMORY; + if (jsf->new_number != NULL) + { + jsf->new_number ((jsps->temp)->text); + } + rcs_free (& jsps->temp); + + if (jsf->label_value_separator != NULL) + jsf->label_value_separator (); + jsps->state = 27; /* sibling followup */ + break; + + default: + return JSON_ILLEGAL_CHARACTER; + break; + } + return JSON_OK; + } + +state25: /* open object */ + { + switch (c) + { + case '\x20': + case '\x09': + case '\x0A': + case '\x0D': /* JSON insignificant white spaces */ + break; + + case '\"': + jsps->temp = NULL; + jsps->state = 1; + break; + + case '}': + if (jsf->close_object != NULL) + jsf->close_object (); + jsps->state = 26; /* close object */ + break; + + default: + return JSON_ILLEGAL_CHARACTER; + break; + } + return JSON_OK; + } + +state26: /* close object/array */ + { + switch (c) + { + case '\x20': + case '\x09': + case '\x0A': + case '\x0D': /* JSON insignificant white spaces */ + break; + + case '}': + if (jsf->close_object != NULL) + jsf->close_object (); +/* jsp->state = 26; // close object*/ + break; + + case ']': + if (jsf->close_array != NULL) + jsf->close_array (); +/* jsps->state = 26; // close object/array*/ + break; + + case ',': + if (jsf->sibling_separator != NULL) + jsf->sibling_separator (); + jsps->state = 27; /* sibling followup */ + break; + + default: + return JSON_ILLEGAL_CHARACTER; + break; + } + return JSON_OK; + } + +state27: /* sibling followup */ + { + switch (c) + { + case '\x20': + case '\x09': + case '\x0A': + case '\x0D': /* JSON insignificant white spaces */ + break; + + case '\"': + jsps->state = 1; + jsps->temp = NULL; + break; + + case '{': + if (jsf->open_object != NULL) + jsf->open_object (); + jsps->state = 25; /*open object */ + break; + + case '[': + if (jsf->open_array != NULL) + jsf->open_array (); +/* jsps->state = 0; // redundant*/ + break; + + case 't': + jsps->state = 7; /* parse true: tr */ + break; + + case 'f': + jsps->state = 10; /* parse false: fa */ + break; + + case 'n': + jsps->state = 14; /* parse null: nu */ + break; + + case '0': + jsps->state = 17; /* parse number: 0 */ + if ((jsps->temp = rcs_create (5)) == NULL) + { + return JSON_MEMORY; + } + if (rcs_catc ((jsps->temp), '0') != RS_OK) + { + return JSON_MEMORY; + } + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + jsps->state = 24; /* parse number: decimal */ + if ((jsps->temp = rcs_create (5)) == NULL) + { + return JSON_MEMORY; + } + if (rcs_catc ((jsps->temp), c) != RS_OK) + { + return JSON_MEMORY; + } + break; + + case '-': + jsps->state = 23; /* number: */ + if ((jsps->temp = rcs_create (RSTRING_DEFAULT)) == NULL) + { + return JSON_MEMORY; + } + if (rcs_catc ((jsps->temp), '-') != RS_OK) + { + return JSON_MEMORY; + } + break; + + default: + return JSON_ILLEGAL_CHARACTER; + break; + } + return JSON_OK; + } + + return JSON_UNKNOWN_PROBLEM; +} + + +enum json_error +json_find_first_label (const json_t * object, const char *text_label, json_t **value) +{ + json_t *cursor; + + for (cursor = object->child; cursor != NULL; cursor = cursor->next) + { + if (strcmp (cursor->text, text_label) == 0) { + *value = cursor; + return JSON_OK; + } + } + *value = NULL; + return JSON_UNKNOWN_PROBLEM; +} + diff --git a/src/util/json.h b/src/util/json.h new file mode 100644 index 0000000..c093816 --- /dev/null +++ b/src/util/json.h @@ -0,0 +1,311 @@ +/*************************************************************************** + * Copyright (C) 2007 by Rui Maciel * + * rui.maciel@xxxxxxxxx * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Library General Public License as * + * published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * + * * + * This program 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 Library General Public * + * License along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +/** @file json.h A small library that helps deal with JSON-encoded information + \ingroup JSON + + \note error handling is only in a very rudimentary form. + \author Rui Maciel rui_maciel@xxxxxxxxxxxxxxxxxxxxx + \version v1.1 +*/ + +#include <stdint.h> +#include <stdio.h> + +#ifndef JSON_H +#define JSON_H + +#define JSON_MAX_STRING_LENGTH SIZE_MAX-1 + +struct rui_cstring +{ + char *text; /*<! char c-string */ + size_t length; /*<! put in place to avoid strlen() calls */ + size_t max; /*<! usable memory allocated to text minus the space for the nul character */ +}; + +typedef struct rui_cstring rcstring; + +/** + The descriptions of the json_value node type +**/ + enum json_value_type + { JSON_STRING = 0, JSON_NUMBER, JSON_OBJECT, JSON_ARRAY, JSON_TRUE, JSON_FALSE, JSON_NULL }; + +/** + The error messages produced by the JSON parsers +**/ + enum json_error + { + JSON_OK = 1, /*!< everything went smoothly */ + JSON_INCOMPLETE_DOCUMENT, /*!< the parsed document didn't ended */ + JSON_WAITING_FOR_EOF, /*!< A complete JSON document tree was already finished but needs to get to EOF. Other characters beyond whitespaces produce errors */ + JSON_MALFORMED_DOCUMENT, /* the JSON document which was fed to this parser is malformed */ + JSON_INCOMPATIBLE_TYPE, /*!< the currently parsed type does not belong here */ + JSON_MEMORY, /*!< an error occurred when allocating memory */ + JSON_ILLEGAL_CHARACTER, /*!< the currently parsed character does not belong here */ + JSON_BAD_TREE_STRUCTURE, /*!< the document tree structure is malformed */ + JSON_MAXIMUM_LENGTH, /*!< the parsed string reached the maximum allowed size */ + JSON_UNKNOWN_PROBLEM /*!< some random, unaccounted problem occurred */ + }; + + +/** + The JSON document tree node, which is a basic JSON type +**/ + typedef struct json_value + { + enum json_value_type type; /*!< the type of node */ + char *text; /*!< The text stored by the node. It stores UTF-8 strings and is used exclusively by the JSON_STRING and JSON_NUMBER node types */ + + /* FIFO queue data */ + struct json_value *next; /*!< The pointer pointing to the next element in the FIFO sibling list */ + struct json_value *previous; /*!< The pointer pointing to the previous element in the FIFO sibling list */ + struct json_value *parent; /*!< The pointer pointing to the parent node in the document tree */ + struct json_value *child; /*!< The pointer pointing to the first child node in the document tree */ + struct json_value *child_end; /*!< The pointer pointing to the last child node in the document tree */ + } json_t; + + +/** + The structure holding all information needed to resume parsing +**/ + struct json_parsing_info + { + unsigned int state; /*!< the state where the parsing was left on the last parser run */ + unsigned int lex_state; + rcstring *lex_text; + const char *p; + int string_length_limit_reached; /*!< flag informing if the string limit length defined by JSON_MAX_STRING_LENGTH was reached */ + json_t *cursor; /*!< pointers to nodes belonging to the document tree which aid the document parsing */ + }; + + +/** + The structure which holds the pointers to the functions that will be called by the saxy parser whenever their evens are triggered +**/ + struct json_saxy_functions + { + int (*open_object) (void); + int (*close_object) (void); + int (*open_array) (void); + int (*close_array) (void); + int (*new_string) (char *text); + int (*new_number) (char *text); + int (*new_true) (void); + int (*new_false) (void); + int (*new_null) (void); + int (*label_value_separator) (void); + int (*sibling_separator) (void); + }; + + +/** + The structure holding the information needed for json_saxy_parse to resume parsing +**/ + struct json_saxy_parser_status + { + unsigned int state; /*!< current parser state */ + int string_length_limit_reached; /*!< flag informing if the string limit length defined by JSON_MAX_STRING_LENGTH was reached */ + rcstring *temp; /*!< temporary string which will be used to build up parsed strings between parser runs. */ + }; + + +/** + Buils a json_t document by parsing an open file + @param file a pointer to an object controlling a stream, returned by fopen() + @param document a reference to a json_t pointer, set to NULL, which will store the parsed document + @return a json_error error code according to how the parsing operation went. +**/ + enum json_error json_stream_parse (FILE * file, json_t ** document); + + +/** + Creates a new JSON value and defines it's type + @param type the value's type + @return a pointer to the newly created value structure +**/ + json_t *json_new_value (const enum json_value_type type); + + +/** + Creates a new JSON string and defines it's text + @param text the value's text + @return a pointer to the newly created JSON string value +**/ + json_t *json_new_string (const char *text); + + +/** + Creates a new JSON number and defines it's text. The user is responsible for the number string's correctness + @param text the value's number + @return a pointer to the newly created JSON string value +**/ + json_t *json_new_number (const char *text); + + +/** + Creates a new JSON object + @return a pointer to the newly created JSON object value +**/ + json_t *json_new_object (void); + + +/** + Creates a new JSON array + @return a pointer to the newly created JSON array value +**/ + json_t *json_new_array (void); + + +/** + Creates a new JSON null + @return a pointer to the newly created JSON null value +**/ + json_t *json_new_null (void); + + +/** + Creates a new JSON true + @return a pointer to the newly created JSON true value +**/ + json_t *json_new_true (void); + + +/** + Creates a new JSON false + @return a pointer to the newly created JSON false value +**/ + json_t *json_new_false (void); + + +/** + Frees the memory appointed to the value fed as the parameter, as well as all the child nodes + @param value the root node of the tree being freed +**/ + void json_free_value (json_t ** value); + + +/** + Inserts a child node into a parent node, as well as performs some document tree integrity checks. + @param parent the parent node + @param child the node being added as a child to parent + @return the error code corresponding to the operation result +**/ + enum json_error json_insert_child (json_t * parent, json_t * child); + + +/** + Inserts a label:value pair into a parent node, as well as performs some document tree integrity checks. + @param parent the parent node + @param text_label a char string which serves as the label in the label:value pair + @param value the value in the label:value pair + @return the error code corresponding to the operation result +**/ + enum json_error json_insert_pair_into_object (json_t * parent, const char *text_label, json_t * value); + + +/** + Produces a JSON markup text document from a document tree + @param root The document's root node + @param text a pointer to a char string that will hold the JSON document text. + @return a json_error code describing how the operation went +**/ + enum json_error json_tree_to_string (json_t * root, char **text); + + +/** + Produces a JSON markup text document from a json_t document tree to a text stream + @param file a opened file stream + @param root The document's root node + @return a json_error code describing how the operation went +**/ + enum json_error json_stream_output (FILE * file, json_t * root); + + +/** + Strips all JSON white spaces from the text string + @param text a char string holding a JSON document or document snippet +**/ + void json_strip_white_spaces (char *text); + + +/** + Formats a JSON markup text contained in the given string + @param text a JSON formatted document + @return a pointer to a char string holding the formated document +**/ + char *json_format_string (const char *text); + + +/** + Outputs a new UTF8 c-string which replaces all characters that must be escaped with their respective escaped versions + @param text an UTF8 char text string + @return an UTF-8 c-string holding the same text string but with escaped characters +**/ + char *json_escape (char *text); + + +/** + This function takes care of the tedious task of initializing any instance of + struct json_parsing_info + @param jpi a pointer to a struct json_parsing_info instance +**/ + void json_jpi_init (struct json_parsing_info *jpi); + + +/** + Produces a document tree sequentially from a JSON markup text fragment + @param info the information necessary to resume parsing any incomplete document + @param buffer a null-terminated c-string containing a JSON document fragment + @return a code describing how the operation ended up +**/ + enum json_error json_parse_fragment (struct json_parsing_info *info, const char *buffer); + + +/** + Produces a document tree from a JSON markup text string that contains a complete document + @param root a reference to a pointer to a json_t type. The function allocates memory to the passed pointer and sets up the value + @param text a c-string containing a complete JSON text document + @return a pointer to the new document tree or NULL if some error occurred +**/ + enum json_error json_parse_document (json_t ** root, const char *text); + + +/** + Function to perform a SAX-like parsing of any JSON document or document fragment that is passed to it + @param jsps a structure holding the status information of the current parser + @param jsf a structure holding the function pointers to the event functions + @param c the character to be parsed + @return a json_error code informing how the parsing went +**/ + enum json_error json_saxy_parse (struct json_saxy_parser_status *jsps, struct json_saxy_functions *jsf, char c); + + +/** + Searches through the object's children for a label holding the text text_label + @param object a json_value of type JSON_OBJECT + @param text_label the c-string to search for through the object's child labels + @return a pointer to the first label holding a text equal to text_label or NULL if there is no such label or if object has no children +**/ + enum json_error json_find_first_label (const json_t * object, const char *text_label, json_t **value); + + +#endif -- 1.6.2.5 -- Libvir-list mailing list Libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list